Skip to content

7. معماری لایه‌ای و برنامه‌نویسی مبتنی بر رابط

7.1. Introduction

ما پیشنهاد می‌کنیم برنامه‌ای بنویسیم که نمرات دانش‌آموزان یک دبیرستان را نمایش دهد. این برنامه دارای معماری چندلایه خواهد بود:

  • لایه [présentation] لایه‌ای است که با کاربر برنامه تعامل دارد.
  • لایه [metier] منطق کسب‌وکار برنامه را پیاده‌سازی می‌کند، مانند محاسبه حقوق یا صدور فاکتور. این لایه داده‌ها را از کاربر از طریق لایه [présentation] و از لایه SGBD از طریق لایه [dao] استفاده می‌کند.
  • لایه [dao] (ابجکت‌های دسترسی به داده) دسترسی به داده‌ها در SGBD را مدیریت می‌کند.

ما این معماری را با یک برنامه کنسول ساده نشان خواهیم داد:

  • هیچ پایگاه‌داده وجود نخواهد داشت،
  • لایه [dao]، اشیاء Eleve، Classe، Matiere و Note را مدیریت می‌کند و امکان مدیریت نمرات دانش‌آموزان را فراهم می‌آورد،
  • لایه [métier] برای محاسبه معیارها بر اساس نمرات یک دانش‌آموز خاص استفاده خواهد شد،
  • لایه [présentation] یک برنامه کنسول خواهد بود که نتایج محاسبه شده توسط لایه [métier] را نمایش می‌دهد.

پروژه Visual Studio برای این برنامه به شرح زیر است:

  

7.2. اشیاء برنامه

اشیاء، کلاس هستند. در اینجا، برای هر یک از اشیاء، چهار کلاس خواهیم داشت: Eleve، Classe، Matiere و Note.

فایل [entites.py] شامل چهار کلاس است.

کلاس [Classe] نمایانگر یک کلاس دبیرستان است:


class Classe:
    # سازنده
    def __init__(self,id,nom):
        #پارامترها ذخیره می‌شوند
        self.id=id
        self.nom=nom

    #toString
    def __str__(self):
        return "Classe[{0},{1}]".format(self.id,self.nom)
  • خطوط ۳–۶: یک کلاس با یک عدد id (خط ۵) و یک nom (خط ۶) تعریف می‌شود؛
  • خطوط ۹–۱۰: متد نمایش کلاس.

کلاس [Matiere] به شرح زیر است:


class Matiere:
    # تولیدکننده
    def __init__(self,id,nom,coefficient):
        #پارامترها ذخیره می‌شوند
        self.id=id
        self.nom=nom
        self.coefficient=coefficient

    #toString
    def __str__(self):
        return "Matiere[{0},{1},{2}]".format(self.id,self.nom,self.coefficient)

  • خطوط ۳–۷: یک موضوع با شماره آن (خط ۵)، نام آن (خط ۶) و وزن آن (خط ۷) تعریف می‌شود؛
  • خطوط ۱۰–۱۱: روش نمایش موضوع.

کلاس [Eleve] به شرح زیر است:


class Eleve:
    # تولیدکننده
    def __init__(self,id,nom,prenom,classe):
        #پارامترها ذخیره می‌شوند
        self.id=id
        self.nom=nom
        self.prenom=prenom
        self.classe=classe

    #toString
    def __str__(self):
        return "Eleve[{0},{1},{2},{3}]".format(self.id,self.prenom,self.nom,self.classe)

  • خطوط ۳–۸: یک شاگرد با شمارهٔ خود (خط ۵)، نام خانوادگی (خط ۶)، نام (خط ۷) و کلاس (خط ۸) شناسایی می‌شود. این پارامتر آخر اشاره‌ای به یک شیء [Classe] است؛
  • خطوط ۱۱–۱۲: روش نمایش دانش‌آموز.

کلاس [Note] به شرح زیر است:


class Note:
    # تولیدکننده
    def __init__(self,id,valeur,eleve,matiere):
        #پارامترها ذخیره می‌شوند
        self.id=id
        self.valeur=valeur
        self.eleve=eleve
        self.matiere=matiere

    #toString
    def __str__(self):
        return "Note[{0},{1},{2},{3}]".format(self.id,self.valeur,self.eleve,self.matiere)

  • خطوط ۳–۸: یک شیء [Note] با شماره آن (خط ۵)، مقدار نمره (خط ۶)، مرجعی به دانش‌آموزی که این نمره را کسب کرده است (خط ۷)، و مرجعی به موضوعی که نمره برای آن اعطا شده است (خط ۸) مشخص می‌شود؛
  • خطوط ۱۱–۱۲: روش نمایش شیء [Note].

7.3. لایه [dao]

لایه [dao] رابط زیر را برای لایه [métier] فراهم می‌کند:

  • getClasses فهرست کلاس‌های دبیرستان را بازمی‌گرداند؛
  • getMatieres فهرست دروس را بازمی‌گرداند؛
  • getEleves فهرست دانش‌آموزان را بازمی‌گرداند؛
  • getNotes فهرست نمرات را بازمی‌گرداند.

لایه [métier] تنها از این متدها استفاده خواهد کرد. این لایه نیازی ندارد که بداند این متدها چگونه پیاده‌سازی شده‌اند. بنابراین این داده‌ها می‌توانند از منابع مختلفی (کد سخت، از یک پایگاه داده، از فایل‌های متنی و غیره) بدون تأثیر بر لایه [métier] تأمین شوند. این روش به برنامه‌نویسی مبتنی بر رابط معروف است.

کلاس [Dao] این رابط را به شرح زیر پیاده‌سازی می‌کند:


# -*- coding=utf-8 -*-

#وارد کردن ماژول entities
from entites import *

class Dao:
    # سازنده
    def __init__(self):
        # نمونه سازی کلاس‌ها
        classe1=Classe(1,"classe1")
        classe2=Classe(2,"classe2")
        self.classes=[classe1,classe2]
        # موضوعات
        matiere1=Matiere(1,"matiere1",1)
        matiere2=Matiere(2,"matiere2",2)
        self.matieres=[matiere1,matiere2]
        # دانش‌آموزان
        eleve11=Eleve(11,"nom1","prenom1",classe1)
        eleve21=Eleve(21,"nom2","prenom2",classe1)
        eleve32=Eleve(32,"nom3","prenom3",classe2)
        eleve42=Eleve(42,"nom4","prenom4",classe2)
        self.eleves=[eleve11,eleve21,eleve32,eleve42]
        # نمره‌ها
        note1=Note(1,10,eleve11,matiere1)
        note2=Note(2,12,eleve21,matiere1)
        note3=Note(3,14,eleve32,matiere1)
        note4=Note(4,16,eleve42,matiere1)
        note5=Note(5,6,eleve11,matiere2)
        note6=Note(6,8,eleve21,matiere2)
        note7=Note(7,10,eleve32,matiere2)
        note8=Note(8,12,eleve42,matiere2)
        self.notes=[note1,note2,note3,note4,note5,note6,note7,note8]

    #-----------
    #رابط
    #-----------
    
    # فهرست کلاس‌ها
    def getClasses(self):
        return self.classes

    # فهرست دروس
    def getMatieres(self):
        return self.matieres
    
    # فهرست دانش‌آموزان
    def getEleves(self):
        return self.eleves

    # فهرست نمرات
    def getNotes(self):
        return self.notes

  • خط ۴: ماژولی که حاوی اشیایی است که توسط لایه [dao] مدیریت می‌شوند، وارد می‌شود؛
  • خط ۸: سازنده هیچ پارامتری ندارد. آن به‌صورت ثابت چهار لیست را درج می‌کند:
    • خطوط ۱۰–۱۲: لیست کلاس‌ها؛
    • خطوط 14–16: لیست موضوعات؛
    • خطوط ۱۸–۲۲: فهرست دانش‌آموزان؛
    • خطوط ۲۴–۳۲: فهرست نمرات.
  • خطوط ۳۹–۵۲: چهار متد رابط لایه [dao] به سادگی ارجاعی به چهار فهرست ایجادشده توسط سازنده بازمی‌گردانند.

یک برنامهٔ آزمایشی ممکن است به این شکل باشد:


# -*- coding=utf-8 -*-

# وارد کردن ماژول موجودیت‌ها و لایه [dao]
from entites import *
from dao import *

# نمونهسازی لایه [dao]
dao=Dao()

# فهرست کلاس‌ها
for classe in dao.getClasses():
    print classe

# فهرست کلاس‌ها
for matiere in dao.getMatieres():
    print matiere

# فهرست کلاس‌ها
for eleve in dao.getEleves():
    print eleve

# فهرست کلاس‌ها
for note in dao.getNotes():
    print note

توضیحات خودگویا هستند. خطوط ۱۱ تا ۲۴ از رابط لایه [dao] استفاده می‌کنند. در اینجا هیچ فرضی در مورد پیاده‌سازی واقعی لایه وجود ندارد. در خط ۸، لایه [dao] نمونه سازی می‌شود. در اینجا فرضیاتی در مورد نام کلاس و نوع سازنده در نظر گرفته شده است. راه‌حل‌هایی در دسترس هستند که امکان اجتناب از این وابستگی را فراهم می‌کنند.

نتایج اجرای این اسکریپت به شرح زیر است:

Classe[1,classe1]
Classe[2,classe2]
Matiere[1,matiere1,1]
Matiere[2,matiere2,2]
Eleve[11,prenom1,nom1,Classe[1,classe1]]
Eleve[21,prenom2,nom2,Classe[1,classe1]]
Eleve[32,prenom3,nom3,Classe[2,classe2]]
Eleve[42,prenom4,nom4,Classe[2,classe2]]
Note[1,10,Eleve[11,prenom1,nom1,Classe[1,classe1]],Matiere[1,matiere1,1]]
Note[2,12,Eleve[21,prenom2,nom2,Classe[1,classe1]],Matiere[1,matiere1,1]]
Note[3,14,Eleve[32,prenom3,nom3,Classe[2,classe2]],Matiere[1,matiere1,1]]
Note[4,16,Eleve[42,prenom4,nom4,Classe[2,classe2]],Matiere[1,matiere1,1]]
Note[5,6,Eleve[11,prenom1,nom1,Classe[1,classe1]],Matiere[2,matiere2,2]]
Note[6,8,Eleve[21,prenom2,nom2,Classe[1,classe1]],Matiere[2,matiere2,2]]
Note[7,10,Eleve[32,prenom3,nom3,Classe[2,classe2]],Matiere[2,matiere2,2]]
Note[8,12,Eleve[42,prenom4,nom4,Classe[2,classe2]],Matiere[2,matiere2,2]]

7.4. لایه [métier]

لایه [métier] رابط زیر را پیاده‌سازی می‌کند:

  • getClasses فهرست کلاس‌های دبیرستان را بازمی‌گرداند؛
  • getMatieres فهرست دروس را بازمی‌گرداند؛
  • getEleves فهرست دانش‌آموزان را بازمی‌گرداند؛
  • getNotes فهرست نمرات را بازمی‌گرداند؛
  • getStatsForEleve نمرات شاگرد شماره idEleve را همراه با اطلاعات مربوط به آن‌ها بازمی‌گرداند: میانگین وزنی، کمترین نمره، بیشترین نمره.

لایه [présentation] تنها از این متدها استفاده خواهد کرد. نیازی ندارد بداند چگونه پیاده‌سازی شده‌اند.

متد getStatsForEleve یک شی از نوع [StatsForEleve] را به شرح زیر بازمی‌گرداند:


class StatsForEleve:
    #سازنده
    def __init__(self, eleve, notes):
        #پارامترها ذخیره می‌شوند
        self.eleve=eleve
        self.notes=notes
        # در صورت عدم وجود نت متوقف می‌شود
        if len(notes)==0:
            return
        #پردازش نمرات
        sommePonderee=0
        sommeCoeff=0
        self.max=-1
        self.min=21
        for note in notes:
            valeur=note.valeur
            coeff=note.matiere.coefficient
            sommeCoeff+=coeff
            sommePonderee+=valeur*coeff
            if valeur<self.min:
                self.min=valeur
            if valeur>self.max:
                self.max=valeur
        #محاسبه میانگین شاگرد
        self.moyennePonderee=float(sommePonderee)/sommeCoeff
        
    #toString
    def __str__(self):
        # مورد دانش‌آموز بدون نمره
        if len(self.notes)==0:
            return "Eleve={0}, notes=[]".format(self.eleve)
        # مورد شاگرد دارای نمره
        str=""
        for note in self.notes:
            str+="{0} ".format(note.valeur)
        return "Eleve={0}, notes=[{1}], max={2}, min={3}, moyenne={4}".format(self.eleve, str, self.max, self.min, self.moyennePonderee)

  • خط ۳: سازنده دو پارامتر می‌گیرد:
    • یک مرجع به دانشجوی از نوع [Eleve] که شاخص‌ها برای او محاسبه می‌شوند؛
    • یک مرجع به نمراتشان، یک لیست از اشیاء [Note].
  • خطوط ۸–۹: اگر فهرست نمرات خالی باشد، فرایند در آنجا متوقف می‌شود.
  • در غیر این صورت، در خطوط ۱۱–۲۵ شاخص‌های زیر محاسبه می‌شوند:
    • self.moyennePonderee: میانگین دانش‌آموز، وزن‌دهی‌شده با ضرایب دروس؛
    • self.min: پایین‌ترین نمره شاگرد؛
    • self.max: بالاترین نمرهٔ آن‌ها.
  • خط ۲۸: متد نمایش کلاس در قالب مشخص‌شده در خط ۳۶.

یک اسکریپت تست برای این کلاس ممکن است به این شکل باشد:


# -*- coding=utf-8 -*-

# وارد کردن ماژول‌های انتیت، لایه [dao] و لایه [metier]
from entites import *
from dao import *
from metier import *

#یک کلاس
classe1=Classe(1,"6e A")
# یک شاگرد در این کلاس
paul_durand=Eleve(1,"durand","paul",classe1)
# سه درس
maths=Matiere(1,"maths",1)
francais=Matiere(2,"francais",2)
anglais=Matiere(3,"anglais",3)
#نمرات شاگرد در این دروس
note_maths=Note(1,10,paul_durand,maths)
note_francais=Note(2,12,paul_durand,francais)
note_anglais=Note(3,14,paul_durand,anglais)
#شاخص‌ها نمایش داده می‌شوند
print StatsForEleve(paul_durand,[note_maths, note_francais,note_anglais])

خروجی صفحه به شرح زیر است:

Eleve=Eleve[1,paul,durand,Classe[1,6e A]], notes=[10 12 14 ], max=14, min=10, moyenne=12.6666666667

بیایید به معماری لایه‌به‌لایهٔ خود بازگردیم:

کلاس [Metier] لایه [métier] را به شرح زیر پیاده‌سازی می‌کند:


class Metier:
    # سازنده
    def __init__(self,dao):
        # مرجع در لایه [dao] ذخیره شده است
        self.dao=dao

    #-----------
    #رابط
    #-----------

    # شاخص‌ها روی نمرات
    def getStatsForEleve(self,idEleve):
        #آمار برای دانشجوی شماره idEleve
        #جستجو برای شاگرد
        trouve=False
        i=0
        eleves=self.getEleves()
        while not trouve and i<len(eleves):
            trouve=eleves[i].id==idEleve
            i+=1
        #آیا جستجو موفقیت‌آمیز بود؟
        if not trouve:
            raise RuntimeError("L'eleve [{0}] n'existe pas".format(idEleve))
        else:
            eleve=eleves[i-1]
        # فهرست تمام نمرات
        notes=[]
        for note in self.getNotes():
            # تمام نمرات دانش‌آموز را به نمرات اضافه کنید
            if note.eleve.id==idEleve:
                notes.append(note)
        # نتیجه را بازگردانید
        return StatsForEleve(eleve,notes)

    #فهرست کلاس‌ها
    def getClasses(self):
        return self.dao.getClasses()
    
    # فهرست دروس
    def getMatieres(self):
        return self.dao.getMatieres()
    
    # فهرست دانش‌آموزان
    def getEleves(self):
        return self.dao.getEleves()
    
    # فهرست نمرات
    def getNotes(self):
        return self.dao.getNotes()

  • خطوط ۳–۵: سازنده یک مرجع به لایه [dao] دریافت می‌کند. لایه [métier] باید این مرجع را داشته باشد. در اینجا، این مرجع از طریق سازندهٔ آن فراهم می‌شود. راه‌حل‌های دیگری نیز قابل تصور است. در یک معماری لایه‌ای با نمایش افقی، هر لایه باید مرجعی به لایهٔ سمت راست خود داشته باشد؛
  • خطوط ۳۶–۴۹: متدهای getClasses، getMatieres، getEleves و getNotes صرفاً فراخوانی را به متدهایی با همان نام در لایه [dao] واگذار می‌کنند؛
  • خط ۱۲: متد getStatsForEleve به عنوان پارامتر شماره دانش‌آموزی را که شاخص‌ها باید برای آن بازگردانده شوند، می‌پذیرد.
  • خط ۱۷: دانشجو در فهرست همه دانشجویان جستجو می‌شود؛
  • خطوط ۱۸–۲۰: حلقه جستجو؛
  • خط ۲۳: اگر شاگرد پیدا نشود، یک استثنا ایجاد می‌شود؛
  • در غیر این صورت، خط ۲۵: دانش‌آموز یافت‌شده ذخیره می‌شود؛
  • خطوط ۲۸–۳۱: ما در میان تمام نمرات مدرسه به دنبال نمرات مربوط به دانش‌آموز ذخیره‌شده می‌گردیم؛
  • پس از یافتن این موارد، می‌توان شیء موردنظر StatsForEleve را ساخت.

7.5. لایه [console]

لایه [console] با اسکریپت زیر پیاده‌سازی شده است:


# -*- coding=utf-8 -*-

# وارد کردن ماژول entities، ماژول [dao] و ماژول [metier]
from entites import *
from dao import *
from metier import *

# ----------- لایه [console]
# نمادسازی لایه [metier]
metier=Metier(Dao())
# درخواست/پاسخ
fini=False
while not fini:
    # سؤال
    print "numero de l'eleve (>=1 et * pour arreter) : "
    # پاسخ
    reponse=raw_input()
    # تمام شد؟
    if reponse.strip()=="*":
        break
    #آیا این ورودی صحیح است؟
    ok=False
    try:
        idEleve=int(reponse,10)
        ok=idEleve>=1
    except:
        pass
    #آیا داده‌ها صحیح هستند؟
    if not ok:
        print "Saisie incorrecte. Recommencez..."
        continue
    #محاسبه
    try:
        print metier.getStatsForEleve(idEleve)
    except RuntimeError,erreur:
        print "L'erreur suivante s'est produite : {0}".format(erreur)

  • خط ۱۰: نمونه‌سازی هر دو لایه [dao] و [métier]. این تنها وابستگی کد ما به پیاده‌سازی این لایه‌ها است؛
  • خط ۳۴: از رابط لایه [métier] استفاده می‌شود؛
  • خط ۱۹: متد strip فاصله‌های خالی ابتدایی و انتهایی را از رشته حذف می‌کند؛
  • خط ۲۰: از break برای خروج از حلقه استفاده می‌شود؛
  • خط ۲۴: تلاشی برای تبدیل رشته وارد شده به یک عدد صحیح اعشاری انجام می‌شود؛
  • خط ۲۹: ok تنها در صورتی درست است که خط ۲۵ اجرا شده باشد؛
  • خط ۳۱: continue امکان راه‌اندازی مجدد حلقه از وسط را فراهم می‌کند؛
  • خط ۳۴: محاسبه شاخص‌ها؛
  • خط ۳۵: استثنای RuntimeError، که ممکن است از لایه [métier] پرتاب شود، گرفته می‌شود.

در اینجا مثالی از اجرا آورده شده است:

numero de l'eleve (>=1 et * pour arreter) :
xx
Saisie incorrecte. Recommencez...
numero de l'eleve (>=1 et * pour arreter) :
-4
Saisie incorrecte. Recommencez...
numero de l'eleve (>=1 et * pour arreter) :
11
Eleve=Eleve[11,prenom1,nom1,Classe[1,classe1]], notes=[10 6 ], max=10, min=6, mo
yenne=7.33333333333
numero de l'eleve (>=1 et * pour arreter) :
111
L'erreur suivante s'est produite : L'eleve [111] n'existe pas
numero de l'eleve (>=1 et * pour arreter) :
*