Skip to content

14. Katmanlı mimari ve arayüz tabanlı programlama

14.1. Giriş

Bir ortaokul öğrencilerinin notlarını görüntüleyen bir uygulama yazmayı planlıyoruz. Bu uygulama çok katmanlı bir mimariye sahip olabilir:

Image

  • [ui] katmanı (Kullanıcı Arayüzü), uygulamanın kullanıcısıyla etkileşimde bulunan katmandır;
  • [métier] katmanı, maaş veya fatura hesaplaması gibi uygulama yönetim kurallarını uygular. Bu katman, [présentation] katmanı aracılığıyla kullanıcıdan gelen verileri ve [dao] katmanı aracılığıyla SGBD katmanından gelen verileri kullanır;
  • [dao] katmanı (Veri Erişim Nesneleri), SGBD (Veritabanı Yönetim Sistemi) verilerine erişimi yönetir.

Bu, |cours sur Python 2|'te kullanılan mimaridir. Ayrıca bir varyant da eklenebilir:

Image

Önceki katman yapısına göre farklılıklar şunlardır:

  • yukarıda [main] olarak adlandırılan bir ana komut dosyası, katmanların oluşturulmasını düzenler;
  • [ui, métier, dao] katmanları artık birbirleriyle zorunlu olarak iletişim kurmaz. İletişim kurmaları gerekiyorsa, [main] komut dosyası onlara ihtiyaç duydukları katmanların referanslarını sağlar;

Kod burada, bir orkestra şefi tarafından yönetilen yetkinlik merkezleri şeklinde düzenlenmiştir:

  • orkestra şefi, ana komut dosyası olan [main]'tir;
  • [ui], [dao] ve [métier] katmanları ise yetkinlik merkezleridir;

Bu yapıya “orkestra yapısı” denilebilir.

14.2. Örnek 1

Katmanlı mimariyi basit bir konsol uygulamasıyla açıklayacağız:

  • veritabanı olmayacak;
  • [dao] katmanı, öğrencilerin notlarını yönetmeye olanak tanıyan Elève, Classe, Matière ve Note varlıklarını yönetecektir;
  • [métier] katmanı, belirli bir öğrencinin notlarına ilişkin göstergelerin hesaplanmasını sağlayacaktır;
  • [ui] katmanı, öğrencilerin sonuçlarını görüntüleyen bir konsol uygulaması olacaktır;

Uygulamanın PyCharm projesi şu şekildedir:

Not: Mavi renkli klasörler, PyCharm projesinin [Sources Root] katmanına aittir.

14.2.1. Uygulamadaki varlıklar

Tek işlevi verileri kapsüllemek olan sınıflara "varlıklar" denir. Bunun için sözlükler kullanılabilir. Sınıfın amacı, nesnede depolanan verilerin geçerliliğini test etmeyi sağlamak ve nesnenin kimliğini bir karakter dizisi biçiminde veren bir yöntem sunmaktır.

14.2.1.1. [Classe] varlığı

[Classe] (Classe.py) varlığı, bir kolej sınıfını temsil eder:


# ithalatlar
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils


class Classe(BaseEntity):
    # sınıf durumundan hariç tutulan öznitelikler
    excluded_keys = []

    # sınıf özellikleri
    @staticmethod
    def get_allowed_keys() -> list:
        # id: sınıf kimliği
        # ad: sınıfın adı
        return BaseEntity.get_allowed_keys() + ["nom"]

    # getter
    @property
    def nom(self: object) -> str:
        return self.__nom

    # setter'lar
    @nom.setter
    def nom(self: object, nom: str):
        # ad, boş olmayan bir karakter dizisi olmalıdır
        if Utils.is_string_ok(nom):
            self.__nom = nom
        else:
            raise MyException(11, f"Le nom de la classe {self.id} doit être une chaîne de caractères non vide")

Notlar

    1. satır: [Classe] varlığı, |La classe BaseEntity| paragrafında incelenen [BaseEntity] varlığından türetilmiştir;
  • 11-16. satırlar: Bir sınıf, bir id numarası ve bir nom (16. satır) ile tanımlanır. [id] özelliği, [BaseEntity] sınıfı tarafından sağlanır ve adı ise [Classe] sınıfı tarafından sağlanır;
  • 18-30. satırlar: [nom] özniteliğinin getter / setter'ları;

14.2.1.2. [Matière] varlığı

[Matière] (matière.py) sınıfı şu şekildedir:


# ithalatlar
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils


class Matière(BaseEntity):
    # sınıf durumundan hariç tutulan özellikler
    excluded_keys = []

    # sınıf özellikleri
    @staticmethod
    def get_allowed_keys() -> list:
        # id: ders kimliği
        # ad: dersin adı
        # katsayı: dersin katsayısı
        return BaseEntity.get_allowed_keys() + ["nom", "coefficient"]

    # getter
    @property
    def nom(self: object) -> str:
        return self.__nom

    @property
    def coefficient(self: object) -> float:
        return self.__coefficient

    #  ayarlayıcılar
    @nom.setter
    def nom(self: object, nom: str):
        # ad, boş olmayan bir karakter dizisi olmalıdır
        if Utils.is_string_ok(nom):
            self.__nom = nom
        else:
            raise MyException(21, f"Le nom de la matière {self.id} doit être une chaîne de caractères non vide")

    @coefficient.setter
    def coefficient(self, coefficient: float):
        # katsayı, 0'dan büyük bir gerçek sayı olmalıdır
        erreur = False
        if isinstance(coefficient, (int, float)):
            if coefficient >= 0:
                self.__coefficient = coefficient
            else:
                erreur = True
        else:
            erreur = True
        # hata mı?
        if erreur:
            raise MyException(22, f"Le coefficient de la matière {self.nom} doit être un réel >=0")

Notlar

    1. satır: [Classe] sınıfı, [BaseEntity] sınıfından türetilmiştir;
  • 11-17. satırlar: Bir ders, [id] numarası, [nom] adı ve [coefficient] katsayısı ile tanımlanır;
  • satır 19-50: sınıfın özniteliklerinin getter ve setter yöntemleri;

14.2.1.3. [Elève] varlığı

[Elève] (élève.py) sınıfı şu şekildedir:


# ithalatlar
from BaseEntity import BaseEntity
from Classe import Classe
from MyException import MyException

from Utils import Utils


class Elève(BaseEntity):
    # sınıf durumundan hariç tutulan özellikler
    excluded_keys = []

    # sınıf özellikleri
    @staticmethod
    def get_allowed_keys() -> list:
        # id: öğrencinin kimlik numarası
        # soyadı: öğrencinin soyadı
        # ad: öğrencinin adı
        # sınıf: öğrencinin sınıfı
        return BaseEntity.get_allowed_keys() + ["nom", "prénom", "classe"]

    # alıcılar
    @property
    def nom(self: object) -> str:
        return self.__nom

    @property
    def prénom(self: object) -> str:
        return self.__prénom

    @property
    def classe(self: object) -> Classe:
        return self.__classe

    #  ayarlayıcılar
    @nom.setter
    def nom(self: object, nom: str) -> str:
        # soyadı, boş olmayan bir karakter dizisi olmalıdır
        if Utils.is_string_ok(nom):
            self.__nom = nom
        else:
            raise MyException(41, f"Le nom de l'élève {self.id} doit être une chaîne de caractères non vide")

    @prénom.setter
    def prénom(self: object, prénom: str) -> str:
        # ad, boş olmayan bir karakter dizisi olmalıdır
        if Utils.is_string_ok(prénom):
            self.__prénom = prénom
        else:
            raise MyException(42, f"Le prénom de l'élève {self.id} doit être une chaîne de caractères non vide")

    @classe.setter
    def classe(self: object, value):
        try:
            # Sınıf türü bekleniyor
            if isinstance(value, Classe):
                self.__classe = value
            # veya bir dict türü
            elif isinstance(value,dict):
                self.__classe=Classe().fromdict(value)
            # veya bir json türü
            elif isinstance(value,str):
                self.__classe = Classe().fromjson(value)
        except BaseException as erreur:
            raise MyException(43, f"L'attribut [{value}] de l'élève {self.id} doit être de type Classe ou dict ou json. Erreur : {erreur}")

Notlar

    1. satır: [Elève] sınıfı, [BaseEntity] sınıfından türetilmiştir;
  • 13-20. satırlar: Bir öğrenci, [id] numarası, [nom] soyadı, [prénom] adı ve [classe] sınıfı ile tanımlanır. Bu son parametre, [Classe] nesnesine bir referanstır;
  • 22-65. satırlar: sınıf özniteliklerinin getter/setter yöntemleri;

14.2.1.4. [Note] varlığı

[Note] (note.py) sınıfı şu şekildedir:


# ithalatlar
from BaseEntity import BaseEntity
from Elève import Elève
from Matière import Matière
from MyException import MyException


class Note(BaseEntity):
    # sınıf durumundan hariç tutulan öznitelikler
    excluded_keys = []

    # sınıf özellikleri
    @staticmethod
    def get_allowed_keys() -> list:
        # id: notun kimliği
        # değer: notun kendisi
        # öğrenci: notla ilgili öğrenci (Öğrenci türü)
        # ders: notla ilgili ders (Ders türü)
        # Dolayısıyla Not nesnesi, bir öğrencinin bir derste aldığı nottur
        return BaseEntity.get_allowed_keys() + ["valeur", "élève", "matière"]

    # alıcılar
    @property
    def valeur(self: object) -> float:
        return self.__valeur

    @property
    def élève(self: object) -> Elève:
        return self.__élève

    @property
    def matière(self: object) -> Matière:
        return self.__matière

    # getter'lar
    @valeur.setter
    def valeur(self: object, valeur: float):
        # not, 0 ile 20 arasında bir gerçek sayı olmalıdır
        if isinstance(valeur, (int, float)) and 0 <= valeur <= 20:
            self.__valeur = valeur
        else:
            raise MyException(31,
                              f"L'attribut {valeur} de la note {self.id} doit être un nombre dans l'intervalle [0,20]")

    @élève.setter
    def élève(self: object, value):
        try:
            # "Öğrenci" türü beklenir
            if isinstance(value, Elève):
                self.__élève = value
            # veya bir dict türü
            elif isinstance(value, dict):
                self.__élève = Elève().fromdict(value)
            # veya json türü
            elif isinstance(value, str):
                self.__élève = Elève().fromjson(value)
        except BaseException as erreur:
            raise MyException(32,
                              f"L'attribut [{value}] de la note {self.id} doit être de type Elève ou dict ou json. Erreur : {erreur}")

    @matière.setter
    def matière(self: object, value):
        try:
            # "Matière" türü bekleniyor
            if isinstance(value, Matière):
                self.__matière = value
            # veya dict türü
            elif isinstance(value, dict):
                self.__matière = Matière().fromdict(value)
            # veya bir json türü
            elif isinstance(value, str):
                self.__matière = Matière().fromjson(value)
        except BaseException as erreur:
            raise MyException(33,
                              f"L'attribut [{value}] de la note {self.id} doit être de type Matière ou dict ou json. Erreur : {erreur}")

Notlar

    1. satır: [Note] sınıfı, [BaseEntity] sınıfından türetilmiştir;
  • 12-20. satırlar: [Note] nesnesi, [id] numarasıyla tanımlanır, [valeur] notunun değeri, bu nota sahip öğrenciye ait [élève] referansı ve notun konusu olan [matière] dersine ait referans ile tanımlanır;
  • 22-75. satırlar: sınıf özniteliklerinin getter ve setter yöntemleri;

14.2.2. Uygulamanın yapılandırılması

[config.py] dosyası, ana komut dosyası [main] (1) ile test komut dosyalarının (2) ortamını yapılandırır. Tüm bu komut dosyalarının kodunun başında bir [import config] komutu bulunur. [python script] komutunun konusu olan betiği içeren klasörün otomatik olarak Python Path.Si'in bir parçası olduğunu hatırlatırız; dolayısıyla [config], [import config] komutuna sahip betiklerle aynı klasörde olduğundan bulunacaktır. [1] ve [2] dosyaları burada aynıdır. Ancak bu her zaman böyle olmayabilir.

[config.sys] dosyası şöyledir:


def configure():
    import os

    # bu komut dosyasının klasörünün mutlak yolu
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # root_dir
    root_dir="C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes"
    # mutlak bağımlılıklar
    absolute_dependencies=[
        # sınıflar ve arayüzler içeren yerel klasörler
        f"{root_dir}/02/entities",
        f"{script_dir}/../entities",
        f"{script_dir}/../interfaces",
        f"{script_dir}/../services",
    ]

    # syspath güncellemesi
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # yapılandırma
    return {}
  • 11-14. satırlar: Python Yolu'na eklenmesi gereken klasörler (sys.path);
  • [f"{root_dir}/02/entities"] klasörü, [BaseEntity] ve [MyException] sınıflarına erişim sağlar;
  • [f"{script_dir}/../entities"] klasörü, [Elève], [Classe], [Matière] ve [Note] sınıflarına erişim sağlar;
  • [f"{script_dir}/../interfaces",] klasörü, uygulamanın arayüzlerine erişim sağlar;
  • [f"{script_dir}/../services"] klasörü, arayüzleri uygulayan sınıflara erişim sağlar;

14.2.3. Varlıkların test edilmesi

Burada, [unittest] adlı bir araç tarafından yürütülen testler yazacağız. PyCharm, çeşitli test çerçeveleriyle birlikte gelir. Bunlardan birinin seçimi, PyCharm yapılandırmasında yapılır:

Image

  • [4]'te ise çeşitli test çerçeveleri mevcuttur:

Image

14.2.3.1. [TestBaseEntity] test sınıfı

[TestBaseEntity] test komut dosyası şu şekilde olacaktır:


import unittest

# uygulamayı yapılandırıyoruz
import config

config = config.configure()


class TestBaseEntity (unittest.TestCase):

    def test_note1(self):
        # ithalatlar
        from Note import Note
        from Elève import Elève
        from Classe import Classe
        from Matière import Matière
        # bir dizeden not oluşturma jSON
        note = Note().fromjson(
            '{"id": 8, "değer": 12, "öğrenci": {"id": 42, "soyadı": "soyadı4", "adı": "adı4", "sınıf": {"id": 2, "ad": "sınıf2"}}, "ders": {"id": 2, "ad": "ders2", "ağırlık": 2}}')
        # doğrulamalar
        self.assertIsInstance(note, Note)
        self.assertIsInstance(note.élève, Elève)
        self.assertIsInstance(note.élève.classe, Classe)
        self.assertIsInstance(note.matière, Matière)


    def test_note2(self):
        # içe aktarmalar
        from Note import Note
        from Elève import Elève
        from Classe import Classe
        from Matière import Matière
        # sözlükten not oluşturma
        note = Note().fromdict(
            {"id": 8, "valeur": 12, "élève": {"id": 42, "nom": "nom4", "prénom": "prénom4",
                                              "classe": {"id": 2, "nom": "classe2"}},
             "matière": {"id": 2, "nom": "matière2", "coefficient": 2}})
        # doğrulamalar
        self.assertIsInstance(note, Note)
        self.assertIsInstance(note.élève, Elève)
        self.assertIsInstance(note.élève.classe, Classe)
        self.assertIsInstance(note.matière, Matière)

if __name__ == '__main__':
    unittest.main()

Notlar

    1. satır: Çeşitli test yöntemlerini sağlayacak olan [unittest] modülünü içe aktarıyoruz;
  • 3-6. satırlar: Testler için gerekli sınıfların bulunabilmesi amacıyla uygulama yapılandırılır;
    1. satır: [unittest] test sınıfı, [unittest.TestCase] sınıfını genişletmelidir;
    1. ve 27. satırlar: Test fonksiyonlarının adı [test] ile başlamalıdır, aksi takdirde tanınmazlar;
  • satır 13-16: İhtiyacımız olan sınıflar içe aktarılır;
  • bu test sınıfında, [BaseEntity.fromdict] (satır 34) ve [BaseEntity.fromjson] (satır 18) yöntemlerinin davranışını doğrulamak istiyoruz. [Note] sınıfı, diğer sınıflara referans olan özelliklere sahiptir. Yukarıdaki iki yöntemin geçerli [Note] nesneleri oluşturduğunu doğrulamak istiyoruz;
    1. satır: jSON nesnesinden [Note] nesnesi oluşturulur;
    1. satır: Oluşturulan nesnenin gerçekten [Note] türü olup olmadığı kontrol edilir. [assertIsInstance] yöntemi, [TestBaseEntity] sınıfının üst sınıfı olan [unittest.TestCase] sınıfına ait bir yöntemdir;
    1. satır: [note.élève]'in gerçekten [Elève] türünde olup olmadığı kontrol edilir;
    1. satır: [note.élève.classe]'in gerçekten [Classe] türünde olup olmadığı kontrol edilir;
    1. satır: [note.matière]'in gerçekten [Matière] türü olup olmadığı kontrol edilir;
  • satır 33-42: [BaseEntity.fromdict] yöntemi için de aynı işlem yapılır;

Testleri yürütmenin birkaç yolu vardır:

  • [1-2]'te, [TestBaseEntity]'i [UnitTest] çerçeve yapısı ile çalıştırırız;
  • [3-5]'te testler başarısız olur. [UnitTests], çalıştırılacak hiçbir test bulamadığını belirtir;

Testlerin başarısız olmasının nedeni, [TestBaseEntity] kodunun yapısından kaynaklanmaktadır:


import unittest

# uygulama yapılandırılıyor
import config

config = config.configure()


class TestBaseEntity(unittest.TestCase):

[UnitTest] çerçevesini engelleyen şey, test sınıfının tanımından (9. satır) önce, 3-6. satırlarda yürütülebilir kod bulunmasıdır.

Bunun üzerine kodu şu şekilde yeniden düzenliyoruz:


import unittest


class TestBaseEntity(unittest.TestCase):

    def setUp(self):
        # uygulama yapılandırılıyor
        import config

        config.configure()

    def test_note1(self):
        

    def test_note2(self):
        


if __name__ == '__main__':
    unittest.main()
  • 6-10. satırlar: [setUp] adlı bir işlev tanımlanır. Bu işlevin özel bir rolü vardır: her test işlevinden (test_note1, test_note2) önce yürütülür;

Bunun ardından, [TestBaseEntity] sınıfının çalıştırılması şu sonuçları verir:

Bu sefer her iki test yöntemi de çalıştırıldı ve testler başarılı oldu.

Bir test başarısız olduğunda ne olduğunu görelim. [test_note1] kodunu şu şekilde değiştirelim:


    def test_note1(self):
        # kasıtlı hata - 1==2 olup olmadığı kontrol ediliyor
        self.assertEqual(1,2)
        # ithalatlar
        from Note import Note

    1. satır: 1==2 olup olmadığı kontrol edilir;

Çalıştırma sonuçları şu şekildedir:

Başarısız olan [2] testine tıklayarak hatanın nedenini öğrenebiliriz:

  • [7-8]'te hatanın nedeni;

Bir test sınıfını çalıştırmanın başka bir yolu da bunu bir terminalde çalıştırmaktır:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>python -m unittest TestBaseEntity.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.026s

OK
  1. satır, her iki testin de başarılı olduğunu gösterir (1==2 hatası giderilmiştir);

Son olarak, [TestBaseEntity] test sınıfını yine bir terminalde çalıştırmanın üçüncü yolu şöyledir. Test sınıfını aşağıdaki 6-7. satırlarla sonlandırıyoruz;



        self.assertIsInstance(note.élève.classe, Classe)
        self.assertIsInstance(note.matière, Matière)


if __name__ == '__main__':
    unittest.main()
    1. satır: [__name__] değişkeni, çalıştırılan komut dosyasına verilen addır. Komut [python script.py] ile başlatılan komut dosyası olduğunda, [__name__] değişkeninin değeri [__main__] olur (tanımlayıcının önünde ve arkasında 2 karakterlik alt çizgi bulunur). Dolayısıyla 7. satır, yalnızca [TestBaseEntity] komut dosyası [python TestBaseEntity.py] komutuyla başlatıldığında yürütülür. [unittest.main()] komutu, [UnitTest] çerçeve yapısı aracılığıyla komut dosyasının yürütülmesini başlatır. İşte bir örnek:

(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>python TestBaseEntity.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.013s

OK

14.2.3.2. [TestEntités] test sınıfı

[TestEntités] test sınıfı şu şekildedir:


import unittest


class TestEntités(unittest.TestCase):
    def setUp(self):
        # uygulama yapılandırılıyor
        import config

        config.configure()

    def test_code1a(self):
        # ithalatlar
        from Elève import Elève
        from MyException import MyException
        # hata kodu
        code = None
        try:
            # geçersiz kimlik
            Elève().fromdict({"id": "x", "nom": "y", "prénom": "z", "classe": "t"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 1)

    def test_code41(self):
        # ithalatlar
        from Elève import Elève
        from MyException import MyException
        # hata kodu
        code = None

        try:
            # geçersiz ad
            Elève().fromdict({"id": 1, "nom": "", "prénom": "z", "classe": "t"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 41)

    def test_code42(self):
        # ithalat
        from Elève import Elève
        from MyException import MyException
        # hata kodu
        code = None
        try:
            # geçersiz ad
            Elève().fromdict({"id": 1, "nom": "y", "prénom": "", "classe": "t"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 42)

    def test_code43(self):
        # ithalat
        from Elève import Elève
        from MyException import MyException
        # hata kodu
        code = None
        try:
            # geçersiz sınıf
            Elève().fromdict({"id": 1, "nom": "y", "prénom": "z", "classe": "t"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 43)

    def test_code1b(self):
        # ithalat
        from Classe import Classe
        from MyException import MyException
        # hata kodu
        code = None
        try:
            # geçersiz kimlik
            Classe().fromdict({"id": "x", "nom": "y"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 1)

    def test_code11(self):
        # ithalat
        from Classe import Classe
        from MyException import MyException

        # hata kodu
        code = None
        try:
            # geçersiz ad
            Classe().fromdict({"id": 1, "nom": ""})
        except MyException as ex:
            code = ex.code
        # doğrulama
        self.assertEqual(code, 11)

    def test_code1c(self):
        # ithalat
        from Matière import Matière
        from MyException import MyException

        # hata kodu
        code = None
        try:
            # geçersiz kimlik
            Matière().fromdict({"id": "x", "nom": "y", "coefficient": "t"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 1)

    def test_code21(self):
        # ithalat
        from Matière import Matière
        from MyException import MyException
        # hata kodu
        code = None
        try:
            # geçersiz ad
            Matière().fromdict({"id": "1", "nom": "", "coefficient": "t"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 21)

    def test_code22(self):
        # ithalat
        from Matière import Matière
        from MyException import MyException
        # hata kodu
        code = None
        try:
            # geçersiz katsayı
            Matière().fromdict({"id": 1, "nom": "y", "coefficient": "t"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 22)

    def test_code1d(self):
        # ithalat
        from Note import Note
        from MyException import MyException
        # hata kodu
        code = None
        try:
            # geçersiz kimlik
            Note().fromdict({"id": "x", "valeur": "x", "élève": "y", "matière": "z"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 1)

    def test_code31(self):
        # ithalat
        from Note import Note
        from MyException import MyException

        # hata kodu
        code = None
        try:
            # geçersiz değer
            Note().fromdict({"id": 1, "valeur": "x", "élève": "y", "matière": "z"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 31)

    def test_code32(self):
        # ithalat
        from Note import Note
        from MyException import MyException

        # hata kodu
        code = None
        try:
            # geçersiz öğrenci
            Note().fromdict({"id": 1, "valeur": 10, "élève": "y", "matière": "z"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 32)

    def test_code33(self):
        # ithalat
        from Elève import Elève
        from Note import Note
        from Classe import Classe
        from MyException import MyException

        # hata kodu
        code = None
        try:
            # geçersiz ders
            classe = Classe().fromdict({"id": 1, "nom": "x"})
            élève = Elève().fromdict({"id": 1, "nom": "a", "prénom": "b", "classe": classe})
            Note().fromdict({"id": 1, "valeur": 10, "élève": élève, "matière": "z"})
        except MyException as ex:
            print(f"\ncode erreur={ex.code}, message={ex}")
            code = ex.code
        # doğrulama
        self.assertEqual(code, 33)

    def test_exception(self):
        # ithalat
        from Elève import Elève
        # testin başarılı olması için [MyException] türünün çalıştırılması gerekir
        from MyException import MyException
        with self.assertRaises(MyException):
            # test
            Elève().fromdict({"id": "x", "nom": "y", "prénom": "z", "classe": "t"})


if __name__ == '__main__':
    unittest.main()
  • Test betiğinin amacı, sınıfların setter yöntemlerini test etmektir: çeşitli varlıkların özniteliklerine hatalı değerler atanamayacağını doğrulamak;
  • 11-24. satırlar: Bir öğrenciye geçersiz bir kimlik numarası atanamayacağını test ediyoruz. 16. satırda öğrencinin kimlik numarası olarak 'x' değerini atadığımız için bir istisna oluşması beklenir. Dolayısıyla 20-22. satırlara geçmemiz gerekir;
    1. satır: hata mesajının görüntülenmesi;
  • satır 22: hata kodu alınır (bkz. paragraf |L'entité MyException|);
    1. satır: hata kodunun 1 olup olmadığı kontrol edilir (assert). Burada iki şey kontrol edilir:
    2. gerçekten bir hata olup olmadığı;
    3. hata kodunun 1 olup olmadığı;
  • bu işlem, 24-213. satırlardaki işlevlerle tekrarlanır;
  • 215-222. satırlar: bir eylemin belirli bir türde bir istisna oluşturup oluşturmadığını test edilir;
    1. satır: [MyException] türünde bir istisna oluşturursa testin başarılı olduğu belirtilir;

Sonuçlar

Test komut dosyası çalıştırılır:

Elde edilen sonuçlar şunlardır:


Testing started at 09:39 ...
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1.2\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py" --path C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestEntités.py
Launching unittests with arguments python -m unittest C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestEntités.py in C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests


code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Elève.Elève'> doit être un entier >=0]

code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Classe.Classe'> doit être un entier >=0]

code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Matière.Matière'> doit être un entier >=0]

code erreur=1, message=MyException[1, L'identifiant d'une entité <class 'Note.Note'> doit être un entier >=0]

code erreur=21, message=MyException[21, Le nom de la matière 1 doit être une chaîne de caractères non vide]

code erreur=22, message=MyException[22, Le coefficient de la matière y doit être un réel >=0]

code erreur=31, message=MyException[31, L'attribut x de la note 1 doit être un nombre dans l'intervalle [0,20]]

code erreur=32, message=MyException[32, L'attribut [y] de la note 1 doit être de type Elève ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]

code erreur=33, message=MyException[33, L'attribut [z] de la note 1 doit être de type Matière ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]

code erreur=41, message=MyException[41, Le nom de l'élève 1 doit être une chaîne de caractères non vide]

code erreur=42, message=MyException[42, Le prénom de l'élève 1 doit être une chaîne de caractères non vide]

code erreur=43, message=MyException[43, L'attribut [t] de l'élève 1 doit être de type Classe ou dict ou json. Erreur : Expecting value: line 1 column 1 (char 0)]


Ran 14 tests in 0.040s

OK

Process finished with exit code 0

Burada tüm testler başarılı olmuştur

14.2.4. [dao] katmanı

Image

[dao] katmanı, [InterfaceDao] [1] arayüzünü uygular. Bu arayüz, [Dao] (2) sınıfı tarafından uygulanır. [tests_dao] komut dosyası (3), [dao] katmanının yöntemlerini test eder.

14.2.4.1. [InterfaceDao] Arayüzü

Bir arayüz, çağıran kod ile çağrılan kod arasında yapılan bir anlaşmadır. Arayüzü sunan, çağrılan koddur:

  • çağıran kod [1], çağrılan kod [3]'in uygulamasını bilmez. Sadece onu nasıl çağıracağını bilir. Bunu ona [2] arayüzü belirtir. Bu arayüz, çağrılan kodla etkileşim kurmak için kullanılacak bir dizi yöntem/fonksiyonu tanımlar. Bu arayüze API (Uygulama Programlama Arayüzü) adı da verilir;

[dao] katmanı şu arayüzü sunacaktır:

  • [get_classes], ortaokul sınıflarının listesini döndürür;
  • [get_matières], ortaokulda öğretilen derslerin listesini verir;
  • [get_élèves], ortaokul öğrencilerinin listesini döndürür;
  • [get_notes], tüm öğrencilerin notlarının listesini verir;
  • [get_notes_for_élève_by_id] belirli bir öğrencinin notlarını gösterir;
  • [get_élève_by_id], numarasıyla belirtilen bir öğrenciyi döndürür;

Çağıran kod yalnızca bu yöntemleri kullanacaktır. Bu yöntemlerin nasıl uygulandığını bilmesi gerekmez. Böylece veriler, çağıran kodu etkilemeden farklı kaynaklardan (sabit veriler, veritabanı, metin dosyaları vb.) gelebilir. Buna arayüz tabanlı programlama denir.

Python 3'te arayüz kavramına yakın bir kavram vardır: soyut sınıf. Bunu kullanacağız. Bu örnekteki arayüzleri [interfaces] klasöründe toplayacağız.

[dao] katmanı için [InterfaceDao] (InterfaceDao.py) adlı bir soyut sınıf tanımlıyoruz:


# ithalatlar
from abc import ABC, abstractmethod

# Dao arayüzü
from Elève import Elève


class InterfaceDao(ABC):
    # sınıf listesi
    @abstractmethod
    def get_classes(self: object) -> list:
        pass

    # öğrenci listesi
    @abstractmethod
    def get_élèves(self: object) -> list:
        pass

    # ders listesi
    @abstractmethod
    def get_matières(self: object) -> list:
        pass

    # not listesi
    @abstractmethod
    def get_notes(self: object) -> list:
        pass

    # bir öğrencinin not listesi
    @abstractmethod
    def get_notes_for_élève_by_id(self: object, élève_id: int) -> list:
        pass

    # bir öğrenciyi kimlik numarasına göre arama
    @abstractmethod
    def get_élève_by_id(self, élève_id: int) -> Elève:
        pass

Notlar:

    1. satır: ABC=Soyut Temel Sınıf. [abc] modülünden, ABC sınıfı ile 10, 15, 20, 25, 30 ve 35. satırlarda kullanılan [abstractmethod] dekoratörü içe aktarılır;
    1. satır: Soyut sınıfın adı [InterfaceDao]'tir ve [ABC] sınıfından türetilmiştir;
  • soyut sınıfın yöntemleri, [@abstractmethod] dekoratörü ile süslenmiştir; bu dekoratör, süslenen yöntemi soyut bir yöntem haline getirir: kodları tanımlanmamıştır. Bununla birlikte, bu yöntemlere kod eklenir: hiçbir şey yapmayan [pass] komutu;
  • [InterfaceDao] soyut sınıfı örneklenemez. Yalnızca [InterfaceDao] sınıfından türetilmiş ve [InterfaceDao] sınıfının tüm yöntemlerini uygulayan sınıflar örneklenebilir. Dolayısıyla, [InterfaceDao] sınıfından türetilen [Dao1] ve [Dao2] adlı iki sınıf oluşturulursa, her ikisi de [InterfaceDao]'in soyut yöntemlerini uygulayacaktır. Bu şekilde, bu sınıfların [InterfaceDao] arayüzünü uyguladıkları söylenebilir;
  • hem arayüzleri hem de soyut sınıfları uygulayan diller, arayüze soyut sınıftan farklı bir rol atfeder. Bir arayüzün özniteliği yoktur ve örneklenemez. Bir sınıf, bir arayüzün tüm yöntemlerini tanımlayarak o arayüzü uygulayabilir;

14.2.4.2. [Dao] Uygulaması

[Dao] (dao.py) sınıfı, [InterfaceDao] arayüzünü aşağıdaki şekilde uygular:


# varlıkların ve arayüzlerin içe aktarılması
from Classe import Classe
from Elève import Elève
from InterfaceDao import InterfaceDao
from Matière import Matière
from MyException import MyException
from Note import Note


# [dao] katmanı, InterfaceDao arayüzünü uygular
class Dao(InterfaceDao):
    # oluşturucu
    # sabit listeler oluşturulur
    def __init__(self):
        # sınıflar örneklenir
        classe1 = Classe().fromdict({"id": 1, "nom": "classe1"})
        classe2 = Classe().fromdict({"id": 2, "nom": "classe2"})
        self.classes = [classe1, classe2]
        # dersler
        matière1 = Matière().fromdict({"id": 1, "nom": "matière1", "coefficient": 1})
        matière2 = Matière().fromdict({"id": 2, "nom": "matière2", "coefficient": 2})
        self.matières = [matière1, matière2]
        # öğrenciler
        élève11 = Elève().fromdict({"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": classe1})
        élève21 = Elève().fromdict({"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": classe1})
        élève32 = Elève().fromdict({"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": classe2})
        élève42 = Elève().fromdict({"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": classe2})
        self.élèves = [élève11, élève21, élève32, élève42]
        # öğrencilerin farklı derslerdeki notları
        note1 = Note().fromdict({"id": 1, "valeur": 10, "élève": élève11, "matière": matière1})
        note2 = Note().fromdict({"id": 2, "valeur": 12, "élève": élève21, "matière": matière1})
        note3 = Note().fromdict({"id": 3, "valeur": 14, "élève": élève32, "matière": matière1})
        note4 = Note().fromdict({"id": 4, "valeur": 16, "élève": élève42, "matière": matière1})
        note5 = Note().fromdict({"id": 5, "valeur": 6, "élève": élève11, "matière": matière2})
        note6 = Note().fromdict({"id": 6, "valeur": 8, "élève": élève21, "matière": matière2})
        note7 = Note().fromdict({"id": 7, "valeur": 10, "élève": élève32, "matière": matière2})
        note8 = Note().fromdict({"id": 8, "valeur": 12, "élève": élève42, "matière": matière2})
        self.notes = [note1, note2, note3, note4, note5, note6, note7, note8]

    # -----------
    # arayüz IDao
    # -----------
    

Notlar:

  • 1-7. satırlar: varlıklar ve [InterfaceDao] arayüzü içe aktarılır;
    1. satır: [Dao] sınıfı, [InterfaceDao] soyut sınıfından türetilmiştir. Bu sınıfın [InterfaceDao] arayüzünü uyguladığını söyleyebiliriz;
    1. satır: Oluşturucunun parametresi yoktur. Dört listeyi sabit olarak oluşturur:
    2. 15-18. satırlar: sınıflar listesi;
    3. 19-22. satırlar: ders listesi;
    4. 23-28. satırlar: öğrenci listesi;
    5. 29-38. satırlar: not listesi;
  • satır 40-44: [Interface Dao] arayüzünün yöntemlerinin uygulanması. Burada, Python tarafından verilen hata mesajını görmek için bunları tanımlamıyoruz;

Bir test programı şu şekilde olabilir: [tests-dao.py]:


# uygulama yapılandırılıyor
import config

config = config.configure()

# katmanın örneklenmesi [dao]
from Dao import Dao

daoImpl = Dao()

# sınıf listesi
for classe in daoImpl.get_classes():
    print(classe)

# konu listesi
for matière in daoImpl.get_matières():
    print(matière)

# sınıf listesi
for élève in daoImpl.get_élèves():
    print(élève)

# not listesi
for note in daoImpl.get_notes():
    print(note)

Not: [tests-dao.py] betiği, [unittest] testi değildir çünkü adı [test_] ile başlayan yöntemler içermez.

Yorumlar kendiliğinden anlaşılır niteliktedir. 11-25. satırlar, [dao] katmanının arayüzünü kullanır. Burada, katmanın gerçek uygulaması hakkında herhangi bir varsayımda bulunulmamıştır. 9. satırda, [dao] katmanının bir örneği oluşturulur.

Bu komut dosyasının çalıştırılması sonucunda elde edilen sonuçlar şunlardır:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/tests_dao.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/tests_dao.py", line 9, in <module>
    daoImpl = Dao()
TypeError: Can't instantiate abstract class Dao with abstract methods get_classes, get_matières, get_notes, get_notes_for_élève_by_id, get_élève_by_id, get_élèves

Process finished with exit code 1

Görüldüğü gibi, [Dao] sınıfı örneklendiğinde (yukarıdaki 3. satır) bir hata oluşuyor. Python 3 yorumlayıcısı, [get_classes, get_matières, get_notes, get_notes_for_élève_by_id, get_élève_by_id, get_élèves] soyut yöntemlerini tanımlamadığımız için sınıfı örneklendiremediğini bildiriyor.

Pycharm’da da soyut sınıf kavramı mevcuttur ve bize bu sınıfın yöntemlerini tanımlamamızı önerir:

  • [1]'te, kodun üzerine sağ tıklayın;
  • [2-3]'e, [Generate / Implement Methods]'i seçerek [Dao] sınıfının eksik yöntemlerini uygulayın;
  • [4]'te, uygulanacak yöntemleri seçin; burada hepsi seçilmelidir;

Bu işlem tamamlandıktan sonra, [Dao] sınıfı, PyCharm ile aşağıdaki şekilde tamamlanır:


    # -----------
    # arayüz IDao
    # -----------

    def get_classes(self: object) -> list:
        pass

    def get_élèves(self: object) -> list:
        pass

    def get_matières(self: object) -> list:
        pass

    def get_notes(self: object) -> list:
        pass

    def get_notes_for_élève_by_id(self: object, élève_id: int) -> list:
        pass

    def get_élève_by_id(self, élève_id: int) -> Elève:
        pass

[Dao] sınıfını şu şekilde tamamlıyoruz:


    # -----------
    # arayüz IDao
    # -----------
    
    # sınıf listesi
    def get_classes(self) -> list:
        return self.classes

    # ders listesi
    def get_matières(self) -> list:
        return self.matières

    # öğrenci listesi
    def get_élèves(self) -> list:
        return self.élèves

    # not listesi
    def get_notes(self) -> list:
        return self.notes

    def get_notes_for_élève_by_id(self, élève_id: int) -> dict:
        # öğrenci aranıyor
        élève = self.get_élève_by_id(élève_id)
        # notlarını al
        notes = list(filter(lambda n: n.élève.id == élève_id, self.get_notes()))
        # sonuç gösteriliyor
        return {"élève": élève, "notes": notes}

    def get_élève_by_id(self, élève_id: int) -> Elève:
        # öğrencileri filtreleme
        élèves = list(filter(lambda e: e.id == élève_id, self.get_élèves()))
        # bulundu mu?
        if not élèves:
            raise MyException(10, f"L'élève d'identifiant {élève_id} n'existe pas")
        # sonuç
        return élèves[0]
  • 5-19. satırlar sorun teşkil etmez;
  • 29-36. satırlar: numarası verilen öğrenciyi döndüren yöntem. Öğrenci mevcut değilse, bir istisna atılır;
    1. satır: [filter] işlevi, bir listeyi filtrelemeye olanak tanır:
      1. parametre filtreleme kriteridir;
    2. ikinci parametre ise filtrelenecek listedir; bu örnekte öğrenci listesi;
    1. satır: Listenin filtreleme kriteri, [f(e :Elève)->bool] işlevi kullanılarak uygulanır. Bu işlev, filtrelenecek listenin her bir elemanına uygulanır. Eleman filtreleme kriterini karşılıyorsa filtrelenmiş listeye alınır, aksi takdirde listeden çıkarılır. Burada şunlardan birini yapabilirsiniz:
    2. f işlevinin adını belirtip bu işlevi başka bir yerde uygulayabiliriz. Bu durumda [filter] işlevine yapılan çağrı, [filter(f,self.get_élèves()] olur;
    3. f işlevinin tanımını verebiliriz. Bu durumda [filter] işlev çağrısı, [filter(f(e :Elève){…},self.get_élèves()] olur; burada [e], filtrelenmiş listenin bir elemanını, yani bir öğrenciyi temsil eder. Burada yapılan da budur. Burada f fonksiyonunun tanımı [f(e :Elève){return e.id==élève_id)] olur: bir öğrenci, ancak [id] numarası aranan numarayla eşleşmiyorsa seçilir. Böyle bir fonksiyon, lambda fonksiyonu olarak adlandırılan bir fonksiyonla değiştirilebilir: [lambda e: e.id == élève_id]:
      • e: f fonksiyonunun parametresini temsil eder; burada bir öğrenci. İstediğimiz herhangi bir isim kullanılabilir;
      • e.id==élève_id filtreleme kriteridir: [e] numaralı öğrenci, yalnızca [id] numarası aranan numarayla eşleşirse seçilir;
    1. satır: [filter] işlevi, filtrelenmiş listeyi [list] türü olmayan, ancak [list] türüne dönüştürülebilen bir türde döndürür. Burada [list(liste filtrée)] ifadesiyle bunu yapıyoruz;
  • 33-34. satırlar: Filtrelenmiş liste boşsa, aranan öğrenci mevcut değildir. Bu durumda bir istisna atılır;
    1. satır: Buraya gelindiğinde, bir istisna oluşmamış demektir. Bu durumda, 1 elemanlı bir liste elde edildiği anlaşılır (aynı [id] numarasına sahip iki öğrenci yoktur). Dolayısıyla listenin ilk elemanı döndürülür;
  • 21-27. satırlar: [get_notes_for_élève_by_id] yöntemi, kendisine [id] numarası verilen öğrencinin notlarını döndürmelidir;
  • 22-23. satırlar: Öncelikle, az önce yorumladığımız [get_élève_by_id] yöntemi kullanılarak [élève_id] numaralı öğrenci aranır. Aranan öğrenci mevcut değilse bir istisna oluşabilir. 23. satırdaki komutun etrafında try / catch bloğu bulunmadığından, istisna çağıran koda iletilecektir. İstenen de budur;
  • 24-25. satırlar: Öğrenci alındıktan sonra, tüm notları alınır. Bunu yine bir filtre kullanarak yaparız:
    • filtre [filter(critère, self_getnotes()]'tir. Dolayısıyla filtrelenecek liste, okulun tüm öğrencilerinin tüm notlarından oluşan listedir;
    • filtreleme kriteri, [lambda] fonksiyonu kullanılarak ifade edilir: lambda n: n.élève.id == élève_id. n parametresi, filtrelenecek listenin bir elemanıdır, yani bir nottur. [Note] türü, notun sahibi olan öğrenciyi temsil eden [élève] özelliğine sahiptir. Bu durumda, bu öğrencinin numarasını temsil eden [n.élève.id]'in, aranan öğrencinin numarasıyla eşit olması gerekir;

Ardından [tests-dao.py] komut dosyasını çalıştırıyoruz.


# uygulamayı yapılandırıyoruz
import config

config = config.configure()

# [dao] katmanının örneği oluşturuluyor
from Dao import Dao

daoImpl = Dao()

# sınıf listesi
for classe in daoImpl.get_classes():
    print(classe)

# konu listesi
for matière in daoImpl.get_matières():
    print(matière)

# sınıf listesi
for élève in daoImpl.get_élèves():
    print(élève)

# not listesi
for note in daoImpl.get_notes():
    print(note)

# belirli bir öğrenci
print(daoImpl.get_élève_by_id(11))

# not listesi
dict1 = daoImpl.get_notes_for_élève_by_id(11)
print(f"élève n° 11 = {dict1['élève']}")
for note in dict1["notes"]:
    print(f"note de l'élève n° 11 = {note}")

Böylece şu sonuçları elde ederiz:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/tests_dao.py
{"id": 1, "nom": "classe1"}
{"id": 2, "nom": "classe2"}
{"id": 1, "nom": "matière1", "coefficient": 1}
{"id": 2, "nom": "matière2", "coefficient": 2}
{"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
{"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}
{"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}
{"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}
{"id": 1, "valeur": 10, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 2, "valeur": 12, "élève": {"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 3, "valeur": 14, "élève": {"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 4, "valeur": 16, "élève": {"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
{"id": 5, "valeur": 6, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 6, "valeur": 8, "élève": {"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 7, "valeur": 10, "élève": {"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 8, "valeur": 12, "élève": {"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": {"id": 2, "nom": "classe2"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}
{"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
élève n° 11 = {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}
note de l'élève n° 11 = {"id": 1, "valeur": 10, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 1, "nom": "matière1", "coefficient": 1}}
note de l'élève n° 11 = {"id": 5, "valeur": 6, "élève": {"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, "matière": {"id": 2, "nom": "matière2", "coefficient": 2}}

Process finished with exit code 0

Bir not görüntülendiğinde (diğer nesneler için de durum benzerdir), şunların da görüntülendiği görülebilir:

  • notun sahibi olan öğrenci;
  • notun referans aldığı ders;

Bu sonucu üreten işlev [BaseEntity.asdict]'tir (bkz. bağlantı paragrafı).

14.2.5. [métier] katmanı

  • [InterfaceMétier], [métier] katmanının arayüzüdür;
  • [Métier], [métier] katmanının uygulama sınıfıdır;
  • [Testmétier], [Métier] sınıfının bir test sınıfı olan [UnitTest]’tir;

14.2.5.1. [InterfaceMétier] Arayüzü

[métier] katmanı, aşağıdaki [InterfaceMétier] arayüzünü (InterfaceMétier.py) uygulayacaktır:


# ithalatlar
from abc import ABC, abstractmethod

from StatsForElève import StatsForElève


# meslek arayüzü
class InterfaceMétier(ABC):
    # bir öğrenci için istatistik hesaplaması
    @abstractmethod
    def get_stats_for_élève(self, idElève: int) -> StatsForElève:
        pass
  • [get_stats_for_élève], idElève numaralı öğrencinin notlarını ve bunlarla ilgili bilgileri (ağırlıklı ortalama, en düşük not, en yüksek not) döndürür. Bu bilgiler, [StatsForElève] türünde bir nesne içinde kapsüllenmiştir;

14.2.5.2. [StatsForElève] varlığı

Bir öğrencinin istatistiklerini (notlar, en düşük, en yüksek, ağırlıklı ortalama) kapsayan [StatsForElève] (StatsForElève.py) türü şu şekildedir:


# ithalatlar
from BaseEntity import BaseEntity


# belirli bir öğrencinin istatistikleri


class StatsForElève(BaseEntity):
    # sınıf durumundan hariç tutulan özellikler
    excluded_keys = []

    # sınıf özellikleri
    @staticmethod
    def get_allowed_keys() -> list:
        # id: not kimliği
        # öğrenci: ilgili öğrenci
        # notlar: öğrencinin notları
        # moyennePondérée: ders katsayılarına göre ağırlıklı ortalaması
        # min: en düşük notu
        # maks: en yüksek notu

        return BaseEntity.get_allowed_keys() + ["élève", "notes", "moyenne_pondérée", "min", "max"]

    # toString
    def __str__(self) -> str:
        # notu olmayan öğrenci durumu
        if len(self.notes) == 0:
            return f"Elève={self.élève}, notes=[]"
        # notları olan öğrenci durumu
        str = ""
        for note in self.notes:
            str += f"{note.valeur} "
        return f"Elève={self.élève}, notes=[{str.strip()}], max={self.max}, min={self.min}, " \
               f"moyenne pondérée={self.moyenne_pondérée:4.2f}"

Notlar:

    1. satır: [StatsForElève] sınıfı, [BaseEntity] sınıfından türetilmiştir;
  • 13-22. satırlar: sınıfın özellikleri;
    • [BaseEntity]'ten türetilen [id] kimliği;
    • istatistikleri kapsüllenmiş olan [élève] öğrencisi;
    • notları [notes];
    • ağırlıklı ortalaması [moyenne_pondérée];
    • en düşük notu [min];
    • en yüksek notu [max];
  • bu öznitelikler için getter/setter tanımlanmaz. Bu tür nesneleri oluşturanın [métier] katmanı olduğu ve bu katmanın geçersiz nesneler oluşturmayacağı varsayılır;
  • 23-33. satırlar: [__str__] işlevi, nesnenin özelliklerini içeren bir karakter dizesi döndürür;

14.2.5.3. [Métier] uygulaması

[InterfaceMétier] arayüzünün [Métier] (Metier.py) uygulaması şu şekilde olacaktır:


# ithalatlar
from InterfaceDao import InterfaceDao
from InterfaceMétier import InterfaceMétier
from StatsForElève import StatsForElève


class Métier(InterfaceMétier):

    # oluşturucu
    def __init__(self, dao: InterfaceDao):
        # parametreyi kaydediyoruz
        self.__dao = dao

    # -----------
    # arayüz
    # -----------

    # belirli bir öğrencinin notlarına ilişkin göstergeler
    def get_stats_for_élève(self, id_élève: int) -> StatsForElève:
        # idEleve numaralı öğrenciye ait istatistikler
        # id_élève: öğrenci numarası

        # [dao] katmanıyla notları alınır
        notes_élève = self.__dao.get_notes_for_élève_by_id(id_élève)
        élève = notes_élève["élève"]
        notes = notes_élève["notes"]

        # not yoksa işlem durdurulur
        if len(notes) == 0:
            # sonuç verilir
            return StatsForElève().fromdict({"élève": élève, "notes": []})

        # öğrencinin notlarının işlenmesi
        somme_pondérée = 0
        somme_coeff = 0
        max = -1
        min = 21
        for note in notes:
            # not değeri
            valeur = note.valeur
            # ders katsayısı
            coeff = note.matière.coefficient
            # ağırlık katsayılarının toplamı
            somme_coeff += coeff
            # ağırlıklı toplam
            somme_pondérée += valeur * coeff
            # en küçük değerin bulunması
            if valeur < min:
                min = valeur
            # maksimum değerinin bulunması
            if valeur > max:
                max = valeur
        # eksik göstergelerin hesaplanması
        moyenne_pondérée = float(somme_pondérée) / somme_coeff

        # sonuç, [StatsForElève] türü şeklinde verilir
        return StatsForElève(). \
            fromdict({"élève": élève, "notes": notes,
                      "moyenne_pondérée": moyenne_pondérée,
                      "min": min, "max": max})

Notlar

    1. satır: [Métier] sınıfı, [InterfaceMétier] sınıfından türetilmiştir. Bu sınıfın [InterfaceMétier] arayüzünü uyguladığını söylemek yaygın bir uygulamadır;
  • 9-12. satırlar: Oluşturucu, tek parametre olarak [dao] katmanına bir referans alır. 10. satırda, [dao] parametresine [InterfaceDao] türü verildiği dikkat çekmektedir. Kesin bir uygulama beklenmemektedir; sadece [InterfaceDao] arayüzüne uyan bir uygulama yeterlidir. Burada bu önemli değildir, çünkü Python bu türü dikkate almayacaktır; ancak kesin uygulamalar yerine arayüzlerle çalışmak iyi bir alışkanlıktır. Böylece kod daha kolay değiştirilebilir hale gelir;
  • 19-60. satırlar: [get_stats_for_élève] yönteminin uygulaması;
    1. satır: Yöntem, istatistiklerini almak istediğimiz öğrencinin [idElève] numarası olan tek bir parametre alır;
    1. satır: [dao] katmanından öğrencinin notları istenir. Öğrenci mevcut değilse bu istek bir istisna ile sonuçlanır. Bu istisna işlenmez (try / catch yok) ve dolayısıyla çağıran koda geri döner;
    1. satır: İstisna oluşmamışsa buraya gelinir. [notes_élève], bu durumda [élève, note] adlı iki anahtarlı bir sözlük olur:
      1. satır: Öğrenciyle ilgili bilgiler (adı, sınıfı vb.) alınır;
      1. satır: notlar alınır;
  • 28-31. satırlar: öğrencinin notu olup olmadığına bakılır. Notu yoksa, hesaplanacak istatistik yoktur;
  • satır 31: [BaseEntity.fromdict] yöntemi kullanılarak bir sözlükten oluşturulan [StatsForElève] nesnesini döndürür;
  • satır 33-54: öğrencinin notları kullanılarak istenen istatistikler hesaplanır. Koddaki açıklamalar, kodu anlamak için yeterli olmalıdır;
  • satır 56-60: [BaseEntity.fromdict] yöntemi kullanılarak bir sözlükten oluşturulan [StatsForElève] nesnesi döndürülür;

14.2.5.4. [métier] katmanının testi

[métier] katmanına ait bir [UnitTest] betiği şu şekilde olabilir (TestMétier.py):


# içe aktarmalar
import unittest


class Testmétier(unittest.TestCase):
    def setUp(self):
        # uygulama yapılandırılıyor
        import config
        config.configure()

    def test_statsForEleve11(self):
        # ithalatlar
        from Dao import Dao
        from Métier import Métier
        # 11 numaralı öğrencinin göstergelerini test ediyoruz
        dao = Dao()
        stats_for_élève = Métier(dao).get_stats_for_élève(11)
        # görüntüleme
        print(f"\nstats={stats_for_élève}")
        # kontroller
        self.assertEqual(stats_for_élève.min, 6)
        self.assertEqual(stats_for_élève.max, 10)
        self.assertAlmostEqual(stats_for_élève.moyenne_pondérée, 7.333, delta=1e-3)


if __name__ == '__main__':
    unittest.main()

Notlar

  • 6-9. satırlar: Burada [setUp] işlevi, testin Python yolunu yapılandırmak için kullanılmaktadır;
    1. satır: [dao] katmanı örneklenir;
    1. satır: [métier] katmanı oluşturulur ve 11 numaralı öğrencinin istatistiklerini hesaplamak için [get_stats_for_élève] yöntemi kullanılır;
    1. satır: Elde edilen [StatsForElève] sonucunu görüntüliyoruz. [StatsForElève], [BaseEntity]'ten türetildiğinden, burada görüntülenen [StatsForElève]'in jSON dizisidir;
    1. satır: öğrencinin en düşük notu kontrol edilir;
    1. satır: öğrencinin en yüksek notu kontrol edilir;
    1. satır: ağırlıklı ortalamanın 10⁻³ hassasiyetle 7,333'e eşit olup olmadığı test edilir. Genel olarak, gerçek sayıları tam olarak karşılaştırmak mümkün değildir çünkü dahili olarak çoğu zaman sadece yaklaşık bir temsiline sahiptirler;

Test sonuçları şöyledir:


Testing started at 18:17 ...
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1.2\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py" --path C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestMétier.py
Launching unittests with arguments python -m unittest C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/tests/TestMétier.py in C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests



Ran 1 test in 0.015s

OK

stats=Elève={"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, notes=[10 6], max=10, min=6, moyenne pondérée=7.33

Process finished with exit code 0

14.2.6. [ui] katmanı

Image

  • [1]'te, [ui] katmanının arayüzü;
  • [2]'e, bu arayüzün uygulaması;
  • [3]'te, uygulamanın ana betiği;

14.2.6.1. [InterfaceUi] Arayüzü

[UI] katman arayüzü şu şekilde olacaktır:


# içe aktarmalar
from abc import ABC, abstractmethod


# arayüz UI
class InterfaceUi(ABC):
    # katman çalıştırma UI
    @abstractmethod
    def run(self: object):
        pass

Notlar

  • 9-10. satırlar: [UI] katmanı yalnızca bir yönteme sahip olacaktır: [run];

14.2.6.2. [Console] uygulaması

[console] katmanı, aşağıdaki [Console.py] komut dosyası ile uygulanır:


# katman içe aktarmaları

from InterfaceDao import InterfaceDao
from InterfaceMétier import InterfaceMétier
from InterfaceUi import InterfaceUi

# diğer bağımlılıklar
from MyException import MyException


class Console(InterfaceUi):
    # oluşturucu
    def __init__(self: object, métier: InterfaceMétier):
        # işlev: [métier] katmanı

        # özellikler kaydedilir
        self.métier = métier


        # -----------
        # arayüz
        # -----------

    def run(self):
        # kullanıcı diyaloğu
        fini = False
        while not fini:
            # soru / cevap
            réponse = input("Numéro de l'élève (>=1 et * pour arrêter) : ").strip()
            # bitti mi?
            if réponse == "*":
                break
            # giriş doğru mu?
            ok = False
            try:
                id_élève = int(réponse, 10)
                ok = id_élève >= 1
            except ValueError as erreur:
                pass
            # veri doğru mu?
            if not ok:
                print("Saisie incorrecte. Recommencez...")
                continue
            # seçilen öğrenci için istatistik hesaplaması
            try:
                print(self.métier.get_stats_for_élève(id_élève))
            except MyException as erreur:
                print(f"L'erreur suivante s'est produite : {erreur}")
  • 3-5. satırlar: tüm arayüzlerin içe aktarılması;
    1. satır: [Console] sınıfı, [InterfaceUi] arayüzünü uygular;
  • 12-17. satırlar: [Console] sınıfının oluşturucusu, [métier] katmanına bir referans parametre olarak alır. Bu parametreye [InterfaceMétier] türü verilmiş olması, belirli uygulamalardan ziyade arayüzlerle çalıştığımızı hatırlatmak içindir;
    1. satır: arayüzün [run] yönteminin uygulaması;
    1. satır: 31. satırdaki koşul sağlandığında duran bir döngü;
    1. satır: Klavyeden girilen bir verinin alımı. [input] işlevi isteğe bağlı bir parametre alır: girişi istemek için ekrana yazılacak mesaj. Bu giriş her zaman bir karakter dizisi olarak alınır. [strip] işlevi, bu dizinin başındaki veya sonundaki "boşlukları" temizler;
  • satır 34-39: Girilen öğrenci nummasının geçerli olup olmadığı kontrol edilir. Bu, 1'den büyük veya 1'e eşit bir tamsayı olmalıdır. Girilen verinin bir karakter dizisi olarak alındığı unutulmamalıdır;
  • satır 36: Girişi 10 tabanında bir tamsayıya dönüştürmeye çalışılır. Bu mümkün değilse, [int] fonksiyonu bir istisna oluşturur;
  • satır 37: Buraya yalnızca bir istisna oluşmamışsa gelinir. Elde edilen tamsayının gerçekten >=1 olup olmadığı kontrol edilir;
  • satır 38-39: istisna işlenir. Bir istisna oluştuysa, 34. satırdaki [ok] değişkeni [False] olarak kalır;
  • satır 41-43: Giriş hatalıysa, bir hata mesajı görüntülenir ve döngüye geri dönülür (satır 43);
  • satır 45-48: numarası girilen öğrencinin istatistikleri hesaplanır;
    1. satır: [métier] katmanındaki [get_stats_for_élève] yöntemi kullanılır. Öğrenci mevcut değilse bu yöntem bir istisna oluşturur. Bu istisna 47-48. satırlarda işlenir. [dao] ve [métier] katmanlarının [MyException] istisnasını tetiklediği bilinmektedir;

14.3. Ana komut dosyası [main]

Ana komut dosyası [main] şu şekildedir (main.py):


# uygulama yapılandırılıyor
import config

config = config.configure()

# syspath yapılandırıldı - içe aktarmalar yapılabilir
from Console import Console
from Dao import Dao
from Métier import Métier

# ----------- [console] katmanı
try:
    # [dao] katmanının örneklenmesi
    dao = Dao()
    # [métier] katmanının örneklenmesi
    métier = Métier(dao)
    # katman örneği [ui]
    console = Console(métier)
    # [console] katmanının çalıştırılması
    console.run()
except BaseException as ex:
    # hata görüntülenir
    print(f"L'erreur suivante s'est produite : {ex}")
finally:
    pass
  • 1-4. satırlar: Uygulamanın Python Yolu yapılandırılır;
  • 6-9. satırlar: İhtiyacımız olan sınıflar ve arayüzler içe aktarılır;
    1. satır: [dao] katmanının örneklenmesi;
    1. satır: [métier] katmanının örneği oluşturulur;
    1. satır: [ui] katmanının örneği oluşturulur;
    1. satır: kullanıcıyla diyalog başlatılır;
  • satır 13-20: normalde bu satırlardan herhangi bir istisna çıkmaz. [dao] ve [métier] katmanlarından gelen istisnalar, [Console] katmanı tarafından durdurulur. Kullanılan katmanları tam olarak bilmediğinizde (burada durum böyle değildir) istisna yönetimi zor bir sanattır. Şüphe durumunda, yürütülen kod tarafından tetiklenebilecek her türlü istisnayı durdurmak için kod eklenebilir. Burada, 21-23. satırlarda yapılan da budur. [BaseException]'ten türetilen tüm istisnalar, yani tüm istisnalar durdurulur;
  • 24-25. satırlar: [finally] ifadesi burada hiçbir işlev görmez. Bu ifade, yalnızca 21-23. satırları yorum satırına alabilmek için eklenmiştir. Nitekim, hata ayıklama modunda istisnaları durdurmak istenmez. Bu durumda, istisnaları durduran Python yorumlayıcısıdır ve istisnanın meydana geldiği satır numarasını verir. Bu, vazgeçilmez bir bilgidir. 21-23. satırlar yorum satırına alındığında, 24-25. satırların varlığı, sözdizimsel olarak doğru bir try/catch yapısı elde etmeyi sağlar. Bunlar yoksa, Python bir hata bildirir;

İşte bir çalıştırma örneği:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v01/main/main.py
Numéro de l'élève (>=1 et * pour arrêter) : 11
Elève={"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": {"id": 1, "nom": "classe1"}}, notes=[10 6], max=10, min=6, moyenne pondérée=7.33
Numéro de l'élève (>=1 et * pour arrêter) : 1
L'erreur suivante s'est produite : MyException[10, L'élève d'identifiant 1 n'existe pas]
Numéro de l'élève (>=1 et * pour arrêter) : *

Process finished with exit code 0

14.4. Örnek 2

Bu yeni katmanlı mimari örneği, arayüzlerle programlamanın faydalarını göstermeyi amaçlamaktadır. Bu yaklaşım, uygulamaların bakımını ve test edilmesini kolaylaştırır. Yine üç katmanlı bir mimari kullanacağız:

Image

Her katman iki farklı şekilde uygulanacaktır. Bir katmanın uygulamasını, diğerleri üzerinde minimum etkiyle kolayca değiştirebileceğimizi göstermek istiyoruz.

14.4.1. [dao] katmanı

Image

[InterfaceDao] arayüzü şu şekildedir:


# ithalatlar
from abc import ABC, abstractmethod


# Dao arayüzü
class InterfaceDao(ABC):
    # tek bir yöntem
    @abstractmethod
    def do_something_in_dao_layer(self, x: int, y: int) -> int:
        pass
  • 8-10. satırlar: [do_something_in_dao_layer] yöntemi, arayüzün tek yöntemidir;

[DaoImpl1] sınıfı, [InterfaceDao] arayüzünü şu şekilde uygular:


from InterfaceDao import InterfaceDao


class DaoImpl1(InterfaceDao):
    # uygulama InterfaceDao
    def do_something_in_dao_layer(self: InterfaceDao, x: int, y: int) -> int:
        return x + y

[DaoImpl2] sınıfı, [InterfaceDao] arayüzünü şu şekilde uygular:


from InterfaceDao import InterfaceDao


class DaoImpl2(InterfaceDao):
    # uygulama InterfaceDao
    def do_something_in_dao_layer(self: InterfaceDao, x: int, y: int) -> int:
        return x - y

14.4.2. [métier] katmanı

Image

[InterfaceMétier] arayüzü şu şekildedir:


# ithalatlar
from abc import ABC, abstractmethod


# iş arayüzü
class InterfaceMétier(ABC):
    # tek bir yöntem
    @abstractmethod
    def do_something_in_métier_layer(self, x: int, y: int) -> int:
        pass
  • 8-10. satırlar: [do_something_in_métier_layer] yöntemi, arayüzün tek yöntemidir;

[AbstractBaseMétier] sınıfı, [InterfaceMétier] arayüzünü şu şekilde uygular:


# ithalatlar
from abc import ABC, abstractmethod

from InterfaceDao import InterfaceDao
from InterfaceMétier import InterfaceMétier


class AbstractBaseMétier(InterfaceMétier, ABC):
    # özellikler
    # __dao, [dao] katmanında bir referanstır
    @property
    def dao(self) -> InterfaceDao:
        return self.__dao

    @dao.setter
    def dao(self, dao: InterfaceDao):
        self.__dao = dao

    # [InterfaceMétier] arayüzünün uygulaması
    @abstractmethod
    def do_something_in_métier_layer(self, x: int, y: int) -> int:
        pass
    1. satır: [AbstractBaseMétier] sınıfı iki sınıftan türetilmiştir:
    2. [InterfaceMétier]: [AbstractBaseMétier] sınıfı, 19-22. satırlarda bu arayüzü uygular. Aslında, bu sınıfın [do_something_in_métier_layer] yöntemini uygulamadığını ve bu yöntemi soyut olarak tanımladığını (20. satır) görüyoruz. Bu yöntemi uygulamak, türetilen sınıfların görevidir;
    3. [ABC], [@abstractmethod] açıklamalarına erişebilmek için;
    4. sıranın bir önemi vardır: burada sırayı tersine çevirirsek, Python çalışma sırasında bir hata bildirir;

Bu, çoklu miras (birden fazla sınıftan miras alma) ilk kez kullanıldığı durumdur. [AbstractBaseMétier] sınıfı, hem [InterfaceMétier] hem de [ABC] sınıflarının özelliklerini miras alır.

  • 9-17. satırlar: [dao] özelliğini tanımlıyoruz; bu özellik, [dao] katmanına bir referans olacaktır;

Bir arayüz, uygulanmak üzere tasarlanmıştır. Farklı uygulamalar aynı özellikleri paylaştığında, bu özelliklerin tekrarlanmasını önlemek için bunları bir üst sınıfa yerleştirmek yararlıdır. Burada [dao] özelliği için durum böyledir. Üst sınıf, genellikle arayüzün tüm yöntemlerini uygulayamadığı için her zaman soyuttur.

[MétierImpl1] sınıfı, [InterfaceMétier] arayüzünü şu şekilde uygular:


from AbstractBaseMétier import AbstractBaseMétier


class MétierImpl1(AbstractBaseMétier):
    # [InterfaceMétier] arayüzünün uygulaması
    def do_something_in_métier_layer(self:AbstractBaseMétier, x: int, y: int) -> int:
        x += 1
        y += 1
        return self.dao.do_something_in_dao_layer(x, y)
    1. satır: [MétierImpl1] sınıfı, [AbstractbaseMétier] sınıfından türetilmiştir. Dolayısıyla bu sınıfın [dao] özelliğini miras alır;
  • 6-9. satırlar: üst sınıf [AbstractbaseMétier] tarafından uygulanmamış olan [InterfaceMétier] arayüzünün uygulanması;
    1. satır: [dao] katmanı kullanılır;

[MétierImpl2] sınıfı, [InterfaceMétier] arayüzünü benzer şekilde uygular:


from AbstractBaseMétier import AbstractBaseMétier


class MétierImpl2(AbstractBaseMétier):
    # [InterfaceMétier] arayüzünün uygulanması
    def do_something_in_métier_layer(self:AbstractBaseMétier, x: int, y: int) -> int:
        x -= 1
        y -= 1
        return self.dao.do_something_in_dao_layer(x, y)

14.4.3. [ui] katmanı

Image

[InterfaceUi] arayüzü şu şekildedir:


# ithalatlar
from abc import ABC, abstractmethod


# Ui arayüzü
class InterfaceUi(ABC):
    # tek bir yöntem
    @abstractmethod
    def do_something_in_ui_layer(self, x: int, y: int) -> int:
        pass
  • 8-10. satırlar: arayüzün tek yöntemi;

[AbstractBaseUi] sınıfı, [InterfaceUi] arayüzünü şu şekilde uygular:


# ithalatlar
from abc import ABC, abstractmethod

from InterfaceMétier import InterfaceMétier
from InterfaceUi import InterfaceUi


class AbstractBaseUi(InterfaceUi, ABC):
    # özellikler
    # iş modeli, [métier] katmanında bir referanstır
    @property
    def métier(self) -> InterfaceMétier:
        return self.__métier

    @métier.setter
    def métier(self, métier: InterfaceMétier):
        self.__métier = métier

    # [InterfaceUI] arayüzünün uygulaması
    @abstractmethod
    def do_something_in_ui_layer(self: InterfaceUi, x: int, y: int) -> int:
        pass
  • [AbstractBaseUi] sınıfı soyut bir sınıftır (satır 20). [InterfaceUi] arayüzünü uygulamak için bu sınıftan türetilmesi gerekecektir;
  • 9-17. satırlar: [AbstractBaseUi] sınıfı, [métier] katmanına bir referansa sahiptir;

[UiImpl1] uygulama sınıfı şu şekildedir:


from AbstractBaseUi import AbstractBaseUi


class UiImpl1(AbstractBaseUi):
    # [InterfaceUi] arayüzünün uygulaması
    def do_something_in_ui_layer(self: AbstractBaseUi, x: int, y: int) -> int:
        x += 1
        y += 1
        return self.métier.do_something_in_métier_layer(x, y)
    1. satır: [UiImpl1] sınıfı, [AbstractBaseUi] sınıfından türetilmiştir ve dolayısıyla [métier] özelliğini miras almıştır. Bu özellik 9. satırda kullanılmaktadır;

[UiImpl2] uygulama sınıfı da benzerdir:


from AbstractBaseUi import AbstractBaseUi


class UiImpl2(AbstractBaseUi):
    # [InterfaceUi] arayüzünün uygulanması
    def do_something_in_ui_layer(self: AbstractBaseUi, x: int, y: int) -> int:
        x -= 1
        y -= 1
        return self.métier.do_something_in_métier_layer(x, y)
    1. satır: [UiImpl2] sınıfı, [AbstractBaseUi] sınıfından türetilmiştir ve dolayısıyla [métier] özelliğini miras almıştır. Bu özellik 9. satırda kullanılmaktadır;

14.4.4. Yapılandırma dosyaları

Image

  • [config1, config2] dosyaları, uygulamayı iki farklı şekilde yapılandırır;
  • [main] dosyası, uygulamanın ana betiğidir;

[config1] dosyası şöyledir:


def configure():
    # 1. adım ------
    # bu komut dosyasının klasörünün mutlak yolu
    import os
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # bağımlılıklar
    absolute_dependencies = [
        # Python Yolu'ndaki yerel klasörler
        f"{script_dir}/../dao",
        f"{script_dir}/../ui",
        f"{script_dir}/../métier",
    ]

    # syspath yapılandırılıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # 2. adım ------
    # uygulama katmanlarının yapılandırılması
    from DaoImpl1 import DaoImpl1
    from MétierImpl1 import MétierImpl1
    from UiImpl1 import UiImpl1
    # katmanların örneklenmesi
    # DAO
    dao = DaoImpl1()
    # iş mantığı
    métier = MétierImpl1()
    métier.dao = dao
    # kullanıcı arayüzü
    ui = UiImpl1()
    ui.métier = métier

    # katman örneklerini yapılandırmaya ekliyoruz
    # burada sadece ui katmanı gereklidir
    config = {"ui": ui}

    # yapılandırmayı oluşturuyoruz
    return config
  • 2-16. satırlar: uygulamanın Python Yolu'nun yapılandırılması;
  • 18-31. satırlar: [dao, métier, ui] katmanlarının örneklenmesi. Arayüzlerini uygulamak için her seferinde oluşturulan ilk uygulama seçilir;
  • 33-35. satırlar: katman referansları yapılandırmaya eklenir. Burada ana komut dosyasının yalnızca [ui] katmanına ihtiyacı vardır;

[config2] dosyası da benzerdir ve her arayüzü mevcut ikinci uygulamayla uygular:


def configure():
    # 1. adım ---
    # bu komut dosyasının klasörünün mutlak yolu
    import os
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # bağımlılıklar
    absolute_dependencies = [
        # Python yolundaki yerel klasörler
        f"{script_dir}/../dao",
        f"{script_dir}/../ui",
        f"{script_dir}/../métier",
    ]

    # syspath yapılandırılıyor
    from myutils import set_syspath

    set_syspath(absolute_dependencies)

    # 2. adım ------
    # uygulama katmanlarının yapılandırılması
    from DaoImpl2 import DaoImpl2
    from MétierImpl2 import MétierImpl2
    from UiImpl2 import UiImpl2
    # katmanların örneklenmesi
    # DAO
    dao = DaoImpl2()
    # iş mantığı
    métier = MétierImpl2()
    métier.dao = dao
    # kullanıcı arayüzü
    ui = UiImpl2()
    ui.métier = métier

    # katman örneklerini yapılandırmaya ekliyoruz
    # burada sadece ui katmanı gereklidir
    config = {"ui": ui}

    # yapılandırmayı oluşturuyoruz
    return config

14.4.5. Ana komut dosyası [main]

Image

Ana komut dosyası şöyledir:


# ithalatlar
import importlib
import sys

# main ---------

# iki argüman gerekiyor
nb_args = len(sys.argv)
if nb_args != 2 or (sys.argv[1] != "config1" and sys.argv[1] != "config2"):
    print(f"Syntaxe : {sys.argv[0]} config1 ou config2")
    sys.exit()

# uygulama yapılandırması
module = importlib.import_module(sys.argv[1])
config = module.configure()

# katmanın çalıştırılması [ui]
print(config["ui"].do_something_in_ui_layer(10, 20))

Bu komut dosyası bir parametre alır:

  • [config1], yapılandırma no. 1'i kullanmak için;
  • [config2], yapılandırma no. 2'yi kullanmak için;

Python, parametreleri [sys.argv] adlı bir listeye kaydeder:

  • sys.argv[0] komut dosyasının adıdır, burada [main]. Bu parametre her zaman mevcuttur;
  • sys.argv[1], komut dosyasına aktarılan ilk parametredir; sys.argv[2] ise ikinci parametredir, …

    1. satır: parametre sayısı alınır;
  • 9-11. satırlar: Bir parametre olup olmadığı ve değerinin [config1] veya [config2] olup olmadığı kontrol edilir. Aksi takdirde, bir hata mesajı görüntülenir (10. satır) ve programdan çıkılır (11. satır);

İstenen yapılandırma belirlendikten sonra, bu yapılandırmayı uygulamamız gerekir. Örneğin, 1 numaralı yapılandırma seçildiyse, şu kodu çalıştırmamız gerekir:

import config1
config1.configure()

Buradaki sorun, kullanılacak yapılandırmanın bir değişkende bulunmasıdır; bu değişken [len[liste1]]'dir. Adı bir değişkende bulunan bir modülü içe aktarmak için, [importlib] paketini kullanmamız gerekir (satır 2).

    1. satır: adı [email.message_from_bytes(data2[0][1])] içinde bulunan modülü içe aktarıyoruz;
    1. satır: Bu işlem tamamlandıktan sonra, bu modülün [configure] işlevini çalıştırıyoruz. Uygulamanın yapılandırmasını içeren [config] sözlüğünü alıyoruz;
    1. satır: config[‘ui’] içinde [ui] katmanına ait bir referans olduğunu biliyoruz. Bunu, [do_something_in_ui_layer] yöntemini çağırmak için kullanırız. Bu yöntemin, [métier] katmanındaki bir yöntemi çağıracağı ve bu yöntemin de [dao] katmanındaki bir yöntemi çağıracağı bilinmektedir;

Örneğin, [do_something_in_ui_layer] işlevi şöyledir:


class UiImpl1(AbstractBaseUi):
    # arayüzün uygulanması [InterfaceUi]
    def do_something_in_ui_layer(self: AbstractBaseUi, x: int, y: int) -> int:
        x += 1
        y += 1
        return self.métier.do_something_in_métier_layer(x, y)
  • Yukarıdaki 6. satır, 1. satırdaki [UiImpl1] sınıfının [métier] özelliğini kullanır. Oysa [config1] yapılandırmasında şunlar yazılmıştır:

# iş süreci
    métier = MétierImpl1()
    métier.dao = dao
    # kullanıcı arayüzü
    ui = UiImpl1()
    ui.métier = métier
    1. satır: [UIImpl1] nesnesinin [métier] özelliği, [MétierImpl1] sınıfına (2. satır) bir referanstır. Dolayısıyla, [MétierImpl1] sınıfının [do_something_in_ui_layer] yöntemi çalıştırılacaktır;

[MétierUiImpl1] sınıfında şunlar yazmaktadır:


class MétierImpl1(AbstractBaseMétier):
    # QZXW2HTML arayüzünün uygulaması BW0ludGVyZmFjZU3DqXRpZXJdZQX
    def do_something_in_métier_layer(self: AbstractBaseMétier, x: int, y: int) -> int:
        x += 1
        y += 1
        return self.dao.do_something_in_dao_layer(x, y)
    1. satırda, [ui] katmanı tarafından çağrılan yöntem, sırayla [MétierImpl1] sınıfının [dao] özelliğine ait bir yöntemi çağırır;

Oysa [config1] yapılandırmasında şunlar yazılmıştır:


# DAO
    dao = DaoImpl1()
    # iş
    métier = MétierImpl1()
    métier.dao = dao
    1. satır: [MétierImpl1.dao] özelliği, [DaoImpl1] türündedir (2. satır);

Burada göstermek istediğimiz şey, [main] komut dosyasının [métier] ve [dao] katmanlarıyla ilgilenmesine gerek olmadığıdır. Bu komut dosyası yalnızca [ui] katmanıyla ilgilenmelidir; bu katman ile diğerleri arasındaki bağlantılar yapılandırma yoluyla kurulmuştur.

Image

[config1] veya [config2] parametresini [main] komut dosyasına ( ) aktarmak için aşağıdaki şekilde ilerlenir:

Image

  • [1-2]'te, "çalıştırma yapılandırması" adı verilen bir yapılandırma oluşturulur;
  • [3] adımında, bu yapılandırmaya daha sonra bulabilmek için bir ad verilir;
  • [4] adımında, çalıştırılacak komut dosyası seçilir. [1-2] prosedürünü izlediyseniz, doğru komut dosyası zaten seçilmiş olacaktır;
  • [5] adımında, komut dosyasına aktarılacak parametreler buraya girilir. Burada, komut dosyasının 1 numaralı yapılandırmayı kullanmasını istemek için [config1] dizesi aktarılır;
  • [6] adımında, yürütme yapılandırmasını onaylayın;

Image

  • [1-2]'te, mevcut yürütme bağlamlarını görüntülemek istenir;
  • [3] komutunda, mevcut yürütme bağlamı seçilir ve [4] komutuna kopyalanır;

Image

  • [5]'te, yeni yapılandırmaya verilen ad belirlenir. Bu yapılandırma, [main] [6] komut dosyasını, [config2] [7] parametresini aktararak çalıştıracaktır;

Çalıştırma yapılandırmaları, PyCharm penceresinin sağ üst köşesinde bulunur:

Image

[2] veya [3]'i seçip ardından [4]'e basarak, [main] komut dosyasını[config1] veya [config2] parametrelerinden birini kullanarak çalıştırmak yeterlidir.

[config1] ile [main]'in çalıştırılması şu sonuçları verir:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v02/main/main.py config1
34

Process finished with exit code 0

[config2] ile, [main] komutunun çalıştırılması aşağıdaki sonuçları verir:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/troiscouches/v02/main/main.py config2
-10

Process finished with exit code 0

Okuyucunun bu sonuçları kontrol etmesi rica olunur.