Bagaimana kerangka kerja tiOPF untuk delphi / lazarus bekerja. Template Pengunjung

Dari penerjemah


Ada dua alasan mengapa saya berusaha menerjemahkan beberapa bahan pada kerangka kerja yang dikembangkan dua puluh tahun yang lalu untuk lingkungan pemrograman yang tidak terlalu populer:

1. Beberapa tahun yang lalu, setelah mempelajari banyak kesenangan bekerja dengan Entity Framework sebagai ORM untuk platform .Net, saya mencari analog untuk lingkungan Lazarus dan, secara umum, untuk freepascal.
Anehnya, ORM yang baik hilang untuknya. Semua yang ditemukan kemudian adalah proyek open-source yang disebut tiOPF , yang dikembangkan kembali pada akhir 90-an untuk delphi, kemudian diangkut ke freepascal. Namun, kerangka kerja ini secara fundamental berbeda dari tampilan ORM besar dan tebal yang biasa.

Tidak ada metode visual untuk mendesain objek (dalam Entity - model terlebih dahulu) dan memetakan objek ke bidang tabel dalam database relasional (dalam Entity - database terlebih dahulu) di tiOPF. Pengembang sendiri memposisikan fakta ini sebagai salah satu kekurangan proyek, namun, sebagai kelayakan, ia menawarkan orientasi lengkap khususnya pada model bisnis objek, hanya bernilai hardcode ...

Pada level hardcoding yang diusulkan itulah saya mengalami masalah. Pada saat itu, saya tidak terlalu berpengalaman dalam paradigma dan metode yang digunakan oleh pengembang kerangka penuh dan disebutkan dalam dokumentasi beberapa kali per paragraf (pola desain pengunjung, penghubung, pengamat, beberapa tingkat abstraksi untuk independensi DBMS, dll. .). Proyek besar saya yang bekerja dengan database pada waktu itu sepenuhnya berfokus pada komponen visual Lazarus dan cara bekerja dengan database yang ditawarkan oleh lingkungan visual, sebagai akibatnya, banyak kode yang sama: tiga tabel dalam database itu sendiri dengan struktur yang hampir sama dan data yang homogen, tiga bentuk identik untuk dilihat, tiga bentuk identik untuk mengedit, tiga bentuk identik untuk laporan, dan segala sesuatu lainnya dari atas judul "Bagaimana tidak merancang perangkat lunak".

Setelah membaca literatur yang cukup tentang prinsip-prinsip desain database dan sistem informasi yang benar, termasuk studi tentang templat, serta mengenal Kerangka Entitas, saya memutuskan untuk melakukan refactoring penuh baik dari database itu sendiri dan aplikasi saya. Dan jika saya benar-benar mengatasi tugas pertama, maka untuk implementasi yang kedua ada dua jalan menuju arah yang berbeda: baik sepenuhnya untuk mempelajari .net, C # dan Entity Framework, atau menemukan ORM yang cocok untuk sistem Lazarus yang sudah dikenal. Ada juga jejak sepeda ketiga, pertama, tidak mencolok - untuk menulis ORM agar sesuai dengan kebutuhan Anda sendiri, tetapi ini bukan intinya sekarang.

Kode sumber kerangka kerja tidak banyak berkomentar, tetapi para pengembang tetap mempersiapkan (tampaknya pada periode awal pengembangan) sejumlah dokumentasi. Semuanya, tentu saja, berbahasa Inggris, dan pengalaman menunjukkan bahwa, terlepas dari banyaknya kode, diagram, dan frasa pemrograman templat, banyak programmer yang berbahasa Rusia masih kurang berorientasi dalam dokumentasi berbahasa Inggris. Tidak selalu dan tidak semua orang memiliki keinginan untuk melatih kemampuan untuk memahami teks teknis bahasa Inggris tanpa perlu pikiran untuk menerjemahkannya ke dalam bahasa Rusia.

Selain itu, pengoreksian berulang teks untuk terjemahan memungkinkan Anda untuk melihat apa yang saya lewatkan ketika saya pertama kali bertemu dokumentasi, saya tidak memahaminya sepenuhnya atau salah. Artinya, ini untuk dirinya sendiri kesempatan untuk mempelajari kerangka kerja yang dipelajari dengan lebih baik.

2. Dalam dokumentasi, penulis sengaja atau tidak melewatkan beberapa kode, mungkin jelas menurut pendapatnya. Karena keterbatasan penulisan, dokumentasi menggunakan mekanisme dan objek yang sudah ketinggalan zaman sebagai contoh, dihapus atau tidak lagi digunakan dalam versi baru kerangka kerja (tapi bukankah saya mengatakan bahwa itu sendiri terus berkembang?). Juga, ketika saya mengulangi contoh yang dikembangkan sendiri, saya menemukan beberapa kesalahan yang harus diperbaiki. Oleh karena itu, di tempat-tempat saya membiarkan diri saya tidak hanya menerjemahkan teks, tetapi juga untuk menambah atau merevisinya sehingga tetap relevan, dan contoh-contohnya berfungsi.

Saya ingin memulai terjemahan bahan-bahan dari sebuah artikel karya Peter Henrikson tentang "paus" pertama yang menjadi dasar kerangka keseluruhan - templat Pengunjung. Teks asli diposting di sini .

Template pengunjung dan tiOPF


Tujuan artikel ini adalah untuk memperkenalkan templat Pengunjung, yang penggunaannya merupakan salah satu konsep dasar dari kerangka kerja tiOPF (Kerangka Kerja Objek TechInsite). Kami akan mempertimbangkan masalah secara rinci, setelah menganalisis solusi alternatif sebelum menggunakan Pengunjung. Dalam proses mengembangkan konsep pengunjung kami sendiri, kami akan menghadapi tantangan lain: kebutuhan untuk beralih melalui semua objek dalam koleksi. Masalah ini juga akan dipelajari.

Tugas utama adalah membuat cara umum untuk melakukan serangkaian metode terkait pada beberapa objek dalam koleksi. Metode yang dilakukan dapat bervariasi tergantung pada keadaan internal objek. Kami tidak dapat menjalankan metode sama sekali, tetapi kami dapat menjalankan beberapa metode pada objek yang sama.

Level pelatihan yang diperlukan


Pembaca harus terbiasa dengan objek pascal dan menguasai prinsip-prinsip dasar pemrograman berorientasi objek.

Contoh tugas bisnis di artikel ini


Sebagai contoh, kami akan mengembangkan buku alamat yang memungkinkan Anda membuat catatan orang dan informasi kontak mereka. Dengan meningkatnya kemungkinan cara komunikasi antara orang-orang, aplikasi harus secara fleksibel memungkinkan Anda untuk menambahkan metode seperti itu tanpa pemrosesan kode yang signifikan (saya ingat setelah selesai memproses kode untuk menambahkan nomor telepon, saya segera perlu memprosesnya lagi untuk menambahkan email). Kami perlu memberikan dua kategori alamat: nyata, seperti alamat rumah, pos, kantor, dan elektronik: telepon rumah, faks, ponsel, email, situs web.

Pada tingkat presentasi, aplikasi kita akan terlihat seperti Explorer / Outlook, yaitu, seharusnya menggunakan komponen standar seperti TreeView dan ListView. Aplikasi harus bekerja dengan cepat dan tidak memberi kesan perangkat lunak server klien besar.

Aplikasi mungkin terlihat seperti ini:



Di menu konteks pohon, Anda dapat memilih untuk menambah / menghapus kontak seseorang atau perusahaan, dan klik kanan pada daftar data kontak untuk membuka dialog untuk mengeditnya, menghapus atau menambahkan data.

Data dapat disimpan dalam berbagai bentuk, dan di masa mendatang kami akan mempertimbangkan cara menggunakan templat ini untuk mengimplementasikan fitur ini.

Sebelum Anda mulai


Kami akan mulai bekerja dengan kumpulan objek sederhana - daftar orang yang pada gilirannya memiliki dua properti - nama (Nama) dan alamat (EmailAdrs). Untuk mulai dengan, daftar akan diisi dengan data dalam konstruktor, dan kemudian diambil dari file atau database. Tentu saja, ini adalah contoh yang sangat disederhanakan, tetapi cukup untuk sepenuhnya menerapkan template Pengunjung.

Buat aplikasi baru dan tambahkan dua kelas dari bagian antarmuka modul utama: TPersonList (diwarisi dari TObjectList dan membutuhkan plug-in di modul contnrs) dan TPerson (diwarisi dari TObject):

TPersonList = class(TObjectList)  public    constructor Create;  end;  TPerson = class(TObject)  private    FEMailAdrs: string;    FName: string;  public    property Name: string read FName write FName;    property EMailAdrs: string read FEMailAdrs write FEMailAdrs;  end; 

Di konstruktor TPersonList, kami membuat tiga objek TPerson dan menambahkan ke daftar:

 constructor TPersonList.Create; var lData: TPerson; begin inherited; lData := TPerson.Create; lData.Name := 'Malcolm Groves'; lData.EMailAdrs := 'malcolm@dontspamme.com';  // (ADUG Vice President) Add(lData); lData := TPerson.Create; lData.Name := 'Don MacRae';  // (ADUG President) lData.EMailAdrs := 'don@dontspamme.com'; Add(lData); lData := TPerson.Create; lData.Name := 'Peter Hinrichsen';  // (Yours truly) lData.EMailAdrs := 'peter_hinrichsen@dontspamme.com'; Add(lData); end; 

Pertama, kita akan menelusuri daftar dan melakukan dua operasi pada setiap elemen daftar. Operasi mirip, tetapi tidak sama: panggilan ShowMessage sederhana untuk menampilkan konten properti Nama dan EmailAdrs objek TPerson. Tambahkan dua tombol ke formulir dan beri nama sesuatu seperti ini:



Dalam lingkup pilihan formulir Anda, Anda juga harus menambahkan properti (atau hanya bidang) dari FPersonList dari tipe TPersonList (jika jenisnya dideklarasikan di bawah formulir, baik mengubah urutan atau membuat deklarasi jenis pendahuluan), dan memanggil konstruktor di pengendali acara onCreate:

 FPersonList := TPersonList.Create; 

Untuk mengosongkan memori dengan benar di event event onClose form, objek ini harus dihancurkan:

 FPersonList.Free. 

Langkah 1. Iterasi Hardcode


Untuk menampilkan nama dari objek TPerson, tambahkan kode berikut ke pengendali event onClick tombol pertama:

 procedure TForm1.Button1Click(Sender: TObject); var i: integer; begin for i := 0 to FPersonList.Count - 1 do   ShowMessage(TPerson(FPersonList.Items[i]).Name); end; 

Untuk tombol kedua, kode penangannya adalah sebagai berikut:

 procedure TForm1.Button2Click(Sender: TObject); var i: integer; begin for i := 0 to FPersonList.Count - 1 do   ShowMessage(TPerson(FPersonList.Items[i]).EMailAdrs); end; 

Berikut adalah beting yang jelas dari kode ini:

  • dua metode yang melakukan hal yang hampir sama. Semua perbedaannya hanya atas nama properti dari objek yang mereka tampilkan;
  • iterasi akan membosankan, terutama ketika Anda dipaksa untuk menulis loop serupa di seratus tempat dalam kode;
  • seorang pemeran keras bagi TPerson penuh dengan situasi luar biasa. Bagaimana jika ada instance TAnimal dalam daftar tanpa properti alamat? Tidak ada mekanisme untuk menghentikan kesalahan dan mempertahankannya dalam kode ini.

Mari kita cari tahu cara meningkatkan kode dengan memperkenalkan abstraksi: kita meneruskan kode iterator ke kelas induk.

Langkah 2. Abstraksi iterator


Jadi, kami ingin memindahkan logika iterator ke kelas dasar. Daftar iterator itu sendiri sangat sederhana:

 for i := 0 to FList.Count - 1 do // -    … 

Sepertinya kami berencana menggunakan template Iterator . Dari buku pada buku pola desain Gang-of-Four , diketahui bahwa Iterator bisa eksternal dan internal. Saat menggunakan iterator eksternal, klien secara eksplisit mengontrol traversal dengan memanggil metode Next (misalnya, enumerasi elemen TCollection dikendalikan oleh metode First, Next, Last). Kami akan menggunakan iterator internal di sini, karena lebih mudah untuk menerapkan traversal pohon dengan bantuannya, yang merupakan tujuan kami. Kami akan menambahkan metode Iterate ke kelas daftar kami dan akan meneruskan metode panggilan balik ke sana, yang harus dilakukan pada setiap elemen daftar. Callback dalam objek pascal dinyatakan sebagai tipe prosedural, kita akan memiliki, misalnya, TDoSomethingToAPerson.

Jadi, kami mendeklarasikan tipe prosedural TDoSomethingToAPerson, yang mengambil satu parameter tipe TPerson. Tipe prosedural memungkinkan Anda untuk menggunakan metode ini sebagai parameter dari metode lain, yaitu menerapkan callback. Dengan cara ini, kita akan membuat dua metode, salah satunya akan menunjukkan properti Nama objek, dan yang lainnya - properti EmailAdrs, dan mereka sendiri akan diteruskan sebagai parameter ke iterator umum. Akhirnya, bagian deklarasi jenis akan terlihat seperti ini:

 { TPerson } TPerson = class(TObject) private   FEMailAdrs: string;   FName: string; public   property Name: string read FName write FName;   property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end; TDoSomethingToAPerson = procedure(const pData: TPerson) of object; { TPersonList } TPersonList = class(TObjectList) public   constructor Create;   procedure   DoSomething(pMethod: TDoSomethingToAPerson); end;   DoSomething: procedure TPersonList.DoSomething(pMethod: TDoSomethingToAPerson); var i: integer; begin for i := 0 to Count - 1 do   pMethod(TPerson(Items[i])); end; 

Sekarang, untuk melakukan tindakan yang diperlukan pada item daftar, kita perlu melakukan dua hal. Pertama, tentukan operasi yang diperlukan menggunakan metode yang memiliki tanda tangan yang ditentukan oleh TDoSomethingToAPerson, dan kedua, tulis panggilan DoSomething dengan pointer ke metode ini yang dilewatkan sebagai parameter. Di bagian uraian formulir, tambahkan dua deklarasi:

 private   FPersonList: TPersonList;   procedure DoShowName(const pData: TPerson);   procedure DoShowEmail(const pData: TPerson); 

Dalam implementasi metode ini, kami menunjukkan:

 procedure TForm1.DoShowName(const pData: TPerson); begin ShowMessage(pData.Name); end; procedure TForm1.DoShowEmail(const pData: TPerson); begin ShowMessage(pData.EMailAdrs); end; 

Kode untuk penangan tombol diubah sebagai berikut:

 procedure TForm1.Button1Click(Sender: TObject); begin FPersonList.DoSomething(@DoShowName); end; procedure TForm1.Button2Click(Sender: TObject); begin FPersonList.DoSomething(@DoShowEmail); end; 

Sudah lebih baik. Kami sekarang memiliki tiga level abstraksi dalam kode kami. Iterator generik adalah metode kelas yang mengimplementasikan kumpulan objek. Logika bisnis (sejauh ini hanya keluaran pesan tanpa akhir melalui ShowMessage) ditempatkan secara terpisah. Pada tingkat presentasi (antarmuka grafis), logika bisnis disebut dalam satu baris.

Sangat mudah untuk membayangkan bagaimana panggilan ke ShowMessage dapat diganti dengan kode yang menyimpan data kami dari TPerson dalam database relasional menggunakan kueri SQL dari objek TQuery. Misalnya, seperti ini:

 procedure TForm1.SavePerson(const pData: TPerson); var lQuery: TQuery; begin lQuery := TQuery.Create(nil); try   lQuery.SQL.Text := 'insert into people values (:Name, :EMailAdrs)';   lQuery.ParamByName('Name').AsString := pData.Name;   lQuery.ParamByName('EMailAdrs').AsString := pData.EMailAdrs;   lQuery.Datababase := gAppDatabase;   lQuery.ExecSQL; finally   lQuery.Free; end; end; 

By the way, ini memperkenalkan masalah baru mempertahankan koneksi ke database. Dalam permintaan kami, koneksi ke database dilakukan melalui beberapa objek global gAppDatabase. Tapi di mana itu akan terletak dan bagaimana cara kerjanya? Selain itu, kita tersiksa pada setiap langkah iterator untuk membuat objek TQuery, mengkonfigurasi koneksi, mengeksekusi kueri dan jangan lupa untuk membebaskan memori. Akan lebih baik untuk membungkus kode ini dalam kelas yang merangkum logika membuat dan mengeksekusi query SQL, serta mengatur dan memelihara koneksi ke database.

Langkah 3. Melewati objek alih-alih meneruskan pointer ke panggilan balik


Melewati objek ke metode iterator kelas dasar akan memecahkan masalah pemeliharaan negara. Kami akan membuat TPersonVisitor kelas Pengunjung abstrak dengan metode Eksekusi tunggal dan meneruskan objek ke metode ini sebagai parameter. Antarmuka Pengunjung abstrak disajikan di bawah ini:

   TPersonVisitor = class(TObject) public   procedure Execute(pPerson: TPerson); virtual; abstract; end; 

Selanjutnya, tambahkan metode Iterate ke kelas TPersonList kami:

 TPersonList = class(TObjectList) public   constructor Create;   procedure Iterate(pVisitor: TPersonVisitor); end; 

Implementasi metode ini adalah sebagai berikut:

 procedure TPersonList.Iterate(pVisitor: TPersonVisitor); var i: integer; begin for i := 0 to Count - 1 do   pVisitor.Execute(TPerson(Items[i])); end; 

Objek Pengunjung yang diimplementasikan dari kelas TPersonVisitor diteruskan ke metode Iterate, dan ketika iterasi melalui item daftar untuk masing-masing, Pengunjung yang ditentukan (metode pelaksanaannya) dipanggil dengan instance TPerson sebagai parameter.

Mari kita buat dua implementasi Pengunjung - TShowNameVisitor dan TShowEmailVistor, yang akan melakukan pekerjaan yang diperlukan. Berikut cara mengisi kembali bagian antarmuka modul:

 { TShowNameVisitor } TShowNameVisitor = class(TPersonVisitor) public   procedure Execute(pPerson: TPerson); override; end; { TShowEmailVisitor } TShowEmailVisitor = class(TPersonVisitor) public   procedure Execute(pPerson: TPerson); override; end; 

Demi kesederhanaan, penerapan metode eksekusi pada mereka masih akan menjadi satu baris - ShowMessage (pPerson.Name) dan ShowMessage (pPerson.EMailAdrs).

Dan ubah kode untuk penangan klik tombol:

 procedure TForm1.Button1Click(Sender: TObject); var lVis: TPersonVisitor; begin lVis := TShowNameVisitor.Create; try   FPersonList.Iterate(lVis); finally   lVis.Free; end; end; procedure TForm1.Button2Click(Sender: TObject); var lVis: TPersonVisitor; begin lVis := TShowEmailVisitor.Create; try   FPersonList.Iterate(lVis); finally   lVis.Free; end; end; 

Sekarang, setelah memecahkan satu masalah, kami menciptakan yang lain untuk diri kami sendiri. Logika iterator diringkas dalam kelas yang terpisah; operasi yang dilakukan selama iterasi dibungkus dengan objek, yang memungkinkan kita untuk menyimpan beberapa informasi tentang keadaan, tetapi ukuran kode telah berkembang dari satu baris (FPersonList.DoSomething (@DoShowName); menjadi sembilan baris untuk setiap penangan tombol. Sekarang ini akan membantu kami - ini adalah Manajer Pengunjung, yang akan mengurus pembuatan dan membebaskan salinan mereka. Secara potensial, kami dapat menyediakan beberapa operasi dengan objek yang akan dilakukan selama iterasi, untuk ini Manajer Pengunjung akan menyimpan daftar mereka dan melewatinya di setiap langkah, Anda . Olnyaya hanya operasi yang dipilih Berikutnya akan jelas menunjukkan manfaat dari pendekatan ini, kita akan menggunakan pengunjung untuk menyimpan data dalam database relasional sebagai menyimpan data operasi sederhana dapat dilakukan oleh tiga operator yang berbeda SQL: CREATE, DELETE dan UPDATE.

Langkah 4. Enkapsulasi Pengunjung Lebih Lanjut


Sebelum melanjutkan, kita harus merangkum logika karya Pengunjung, memisahkannya dari logika bisnis aplikasi sehingga tidak kembali ke sana. Ini akan membawa kita tiga langkah untuk melakukan ini: membuat kelas dasar TVisited dan TVisitor, kemudian kelas dasar untuk objek bisnis dan koleksi objek bisnis, kemudian sedikit menyesuaikan kelas khusus kami TPerson dan TPersonList (atau TPeople) sehingga mereka menjadi pewaris basis dibuat. kelas. Secara umum, struktur kelas akan sesuai dengan diagram seperti itu:



Objek TVisitor mengimplementasikan dua metode: fungsi AcceptVisitor dan prosedur Execute, di mana objek tipe TVisited dilewatkan. Objek TVisited pada gilirannya mengimplementasikan metode Iterate dengan parameter tipe TVisitor. Yaitu, TVisited .Iterate harus memanggil metode Execute pada objek TVisitor yang ditransfer, mengirimkan tautan ke instance-nya sendiri sebagai parameter, dan jika instance adalah kumpulan, metode Execute dipanggil untuk setiap elemen dalam koleksi. Fungsi AcceptVisitor diperlukan karena kami sedang mengembangkan sistem umum. Dimungkinkan untuk meneruskan ke Pengunjung, yang beroperasi hanya dengan tipe TPerson, contoh kelas TDog, misalnya, dan harus ada mekanisme untuk mencegah pengecualian dan mengakses kesalahan karena ketidakcocokan tipe. Kelas TVisited adalah turunan dari kelas TPersistent, karena sedikit kemudian kita perlu mengimplementasikan fungsi yang terkait dengan penggunaan RTTI.

Bagian antarmuka modul sekarang akan seperti ini:

 TVisited = class; { TVisitor } TVisitor = class(TObject) protected   function AcceptVisitor(pVisited: TVisited): boolean; virtual; abstract; public   procedure Execute(pVisited: TVisited); virtual; abstract; end; { TVisited } TVisited = class(TPersistent) public   procedure Iterate(pVisitor: TVisitor); virtual; end; 

Metode kelas abstrak TVisitor akan dilaksanakan oleh ahli waris, dan implementasi umum metode Iterate untuk TVisited diberikan di bawah ini:

 procedure TVisited.Iterate(pVisitor: TVisitor); begin pVisitor.Execute(self); end; 

Pada saat yang sama, metode ini dinyatakan virtual untuk kemungkinan penggantiannya dalam ahli waris.

Langkah 5. Buat objek dan koleksi bisnis bersama


Kerangka kerja kami membutuhkan dua kelas dasar lagi: untuk mendefinisikan objek bisnis dan koleksi objek tersebut. Sebut mereka TtiObject dan TtiObjectList. Antarmuka yang pertama dari mereka:

 TtiObject = class(TVisited) public   constructor Create; virtual; end; 

Nanti dalam proses pengembangan, kita akan memperumit kelas ini, tetapi untuk tugas saat ini, hanya satu konstruktor virtual dengan kemungkinan mengesampingkannya dalam ahli waris sudah cukup.

Kami berencana untuk menghasilkan kelas TtiObjectList dari TVisited untuk menggunakan perilaku dalam metode yang telah diterapkan oleh leluhur (ada juga alasan lain untuk warisan ini yang akan dibahas di tempatnya). Selain itu, tidak ada yang melarang penggunaan antarmuka (interface) alih-alih kelas abstrak.

Bagian antarmuka dari kelas TtiObjectList adalah sebagai berikut:

 TtiObjectList = class(TtiObject) private   FList: TObjectList; public   constructor Create; override;   destructor Destroy; override;   procedure Clear;   procedure Iterate(pVisitor: TVisitor); override;   procedure Add(pData: TObject); end; 

Seperti yang Anda lihat, wadah itu sendiri dengan elemen objek terletak di bagian yang dilindungi dan tidak akan tersedia untuk pelanggan kelas ini. Bagian terpenting dari kelas adalah implementasi metode Iterate yang ditimpa. Jika di kelas dasar metode hanya disebut pVisitor.Execute (mandiri), maka di sini implementasinya terhubung dengan enumerasi daftar:

 procedure TtiObjectList.Iterate(pVisitor: TVisitor); var i: integer; begin inherited Iterate(pVisitor); for i := 0 to FList.Count - 1 do   (FList.Items[i] as TVisited).Iterate(pVisitor); end; 

Penerapan metode kelas lain mengambil satu baris kode tanpa memperhitungkan ekspresi bawaan yang ditempatkan secara otomatis:

 Create: FList := TObjectList.Create; Destroy: FList.Free; Clear: if Assigned(FList) then FList.Clear; Add: if Assigned(FList) then FList.Add(pData); 

Ini adalah bagian penting dari keseluruhan sistem. Kami memiliki dua kelas dasar logika bisnis: TtiObject dan TtiObjectList. Keduanya memiliki metode Iterate dimana sebuah instance dari kelas TVisited dilewatkan. Iterator sendiri memanggil metode Execute dari kelas TVisitor dan meneruskan referensi ke objek itu sendiri. Panggilan ini sudah ditentukan sebelumnya dalam perilaku kelas di tingkat atas warisan. Untuk kelas kontainer, setiap objek yang disimpan dalam daftar juga memiliki metode Iterate, yang disebut dengan parameter tipe TVisitor, yaitu, setiap pengunjung tertentu dijamin akan mem-bypass semua objek yang tersimpan dalam daftar, serta daftar itu sendiri sebagai objek kontainer.

Langkah 6. Membuat manajer pengunjung


Jadi, kembali ke masalah yang kita gambar sendiri pada langkah ketiga. Karena kami tidak ingin membuat dan memusnahkan salinan Pengunjung setiap saat, pengembangan Manajer akan menjadi solusinya. Ini harus melakukan dua tugas utama: mengelola daftar Pengunjung (yang terdaftar seperti itu di bagian inisialisasi modul individu) dan menjalankannya ketika mereka menerima perintah yang sesuai dari klien.
Untuk mengimplementasikan manajer, kami akan melengkapi modul kami dengan tiga kelas tambahan: TVisClassRef, TVisMapping dan TtiVisitorManager.

 TVisClassRef = class of TVisitor; 

TVisClassRef adalah tipe referensi dan menunjukkan nama kelas tertentu - keturunan TVisitor. Arti menggunakan tipe referensi adalah sebagai berikut: ketika metode dasar Execute dengan tanda tangan disebut

 procedure Execute(const pData: TVisited; const pVisClass: TVisClassRef), 

secara internal, metode ini dapat menggunakan ekspresi seperti lVisitor: = pVisClass.Create untuk membuat turunan dari Pengunjung tertentu, tanpa terlebih dahulu mengetahui jenisnya. Yaitu, kelas apa saja - keturunan TVisitor dapat dibuat secara dinamis di dalam metode Execute yang sama ketika meneruskan nama kelasnya sebagai parameter.

Kelas kedua, TVisMapping, adalah struktur data sederhana dengan dua properti: referensi ke tipe TVisClassRef dan Command properti string. Kelas diperlukan untuk membandingkan operasi yang dilakukan dengan namanya (perintah, misalnya, "save") dan kelas Pengunjung, yang dieksekusi perintah ini. Tambahkan kodenya ke proyek:

 TVisMapping = class(TObject) private   FCommand: string;   FVisitorClass: TVisClassRef; public   property VisitorClass: TVisClassRef read FVisitorClass write FVisitorClass;   property Command: string read FCommand write FCommand; end; 

Dan kelas terakhir adalah TtiVisitorManager. Saat kami mendaftarkan Pengunjung menggunakan Manajer, instance kelas TVisMapping dibuat, yang dimasukkan dalam daftar Manajer.
Dengan demikian, dalam Manajer, daftar Pengunjung dibuat dengan pencocokan perintah string, setelah menerima yang akan dieksekusi. Antarmuka kelas ditambahkan ke modul:

 TtiVisitorManager = class(TObject) private   FList: TObjectList; public   constructor Create;   destructor Destroy; override;   procedure RegisterVisitor(const pCommand: string; pVisitorClass: TVisClassRef);   procedure Execute(const pCommand: string; pData: TVisited); end; 

Metode kuncinya adalah RegisterVisitor dan Execute. Yang pertama biasanya disebut di bagian inisialisasi modul, yang menggambarkan kelas Pengunjung, dan terlihat seperti ini:

 initialization  gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowNameVisitor);  gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowEMailAdrsVisitor); 

Kode metode itu sendiri adalah sebagai berikut:

 procedure TtiVisitorManager.RegisterVisitor(const pCommand: string; pVisitorClass: TVisClassRef); var lData: TVisMapping; begin lData := TVisMapping.Create; lData.Command := pCommand; lData.VisitorClass := pVisitorClass; FList.Add(lData); end; 

Tidak sulit untuk memperhatikan bahwa kode ini sangat mirip dengan implementasi Pascal dari templat Pabrik .

Metode Jalankan penting lainnya menerima dua parameter: perintah yang digunakan Pengunjung atau grup mereka untuk diidentifikasi, serta objek data yang metode Iterate akan dipanggil dengan tautan ke instance dari Pengunjung yang diinginkan. Kode lengkap untuk metode Jalankan diberikan di bawah ini:

 procedure TtiVisitorManager.Execute(const pCommand: string; pData: TVisited); var i: integer; lVisitor: TVisitor; begin for i := 0 to FList.Count - 1 do   if SameText(pCommand, TVisMapping(FList.Items[i]).Command) then   begin     lVisitor := TVisMapping(FList.Items[i]).VisitorClass.Create;     try       pData.Iterate(lVisitor);     finally       lVisitor.Free;     end;   end; end; 

Dengan demikian, untuk menjalankan dua Pengunjung yang sebelumnya terdaftar dengan satu tim, kami hanya perlu satu baris kode:

 gTIOPFManager.VisitorManager.Execute('show', FPeople); 

Selanjutnya, kami akan melengkapi proyek kami sehingga Anda dapat memanggil perintah serupa:

 //      gTIOPFManager.VisitorManager.Execute('read', FPeople); //      gTIOPFManager.VisitorManager.Execute('save', FPeople). 

Langkah 7. Menyesuaikan Kelas Logika Bisnis


Menambahkan leluhur dari kelas TtiObject dan TtiObjectList untuk objek bisnis TPerson dan TPeople kami akan memungkinkan kami untuk merangkum logika iterator di kelas dasar dan tidak menyentuhnya lagi, di samping itu, menjadi mungkin untuk mentransfer objek dengan data ke Manajer Pengunjung.

Deklarasi kelas kontainer baru akan terlihat seperti ini:

 TPeople = class(TtiObjectList); 

Bahkan, kelas TPeople bahkan tidak harus mengimplementasikan apa pun. Secara teoritis, kita bisa melakukannya tanpa deklarasi TPeople sama sekali dan menyimpan objek dalam instance dari kelas TtiObjectList, tetapi karena kami berencana untuk menulis Pengunjung yang hanya memproses instance TPeople, kami membutuhkan kelas ini. Dalam fungsi AcceptVisitor, pemeriksaan berikut akan dilakukan:

 Result := pVisited is TPeople. 

Untuk kelas TPerson, kami menambahkan leluhur TtiObject, dan memindahkan dua properti yang ada ke ruang lingkup yang diterbitkan, karena di masa depan kita perlu bekerja melalui RTTI dengan properti ini. Hal inilah yang nantinya akan secara signifikan mengurangi kode yang terlibat dalam memetakan objek dan catatan dalam database relasional:

 TPerson = class(TtiObject) private   FEMailAdrs: string;   FName: string; published   property Name: string read FName write FName;   property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end; 

Langkah 8. Buat tampilan prototipe


Komentar . Dalam artikel asli, GUI didasarkan pada komponen yang dibuat oleh tiOPF untuk kenyamanan bekerja dengan kerangka kerjanya di delphi. Ini adalah analog komponen DB Sadar, yang merupakan kontrol standar seperti label, bidang input, kotak centang, daftar, dll., Tetapi dikaitkan dengan sifat-sifat tertentu objek tiObject dengan cara yang sama seperti komponen tampilan data dikaitkan dengan bidang dalam tabel database. Seiring waktu, penulis kerangka kerja menandai paket dengan komponen visual ini sebagai usang dan tidak diinginkan untuk digunakan.Sebagai gantinya, ia menyarankan untuk membuat tautan antara komponen visual dan properti kelas menggunakan pola desain Mediator. Template ini adalah yang paling penting kedua di seluruh arsitektur kerangka kerja. Deskripsi penulis tentang perantara dicakup oleh artikel yang terpisah, volume yang sebanding dengan manual ini, oleh karena itu saya menawarkan versi saya yang disederhanakan di sini sebagai GUI.

Ganti nama tombol 1 pada formulir proyek menjadi "show command", dan tombol 2 biarkan tanpa handler untuk saat ini, atau segera beri nama "save command". Lemparkan komponen memo pada formulir dan letakkan semua elemen sesuai selera Anda.

Tambahkan kelas Pengunjung yang akan mengimplementasikan perintah show:

Interface -

 TShowVisitor = class(TVisitor) protected   function AcceptVisitor(pVisited: TVisited): boolean; override; public   procedure Execute(pVisited: TVisited); override; end; 

Dan implementasinya adalah -
 function TShowVisitor.AcceptVisitor(pVisited: TVisited): boolean; begin Result := (pVisited is TPerson); end; procedure TShowVisitor.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then   exit; Form1.Memo1.Lines.Add(TPerson(pVisited).Name + ': ' + TPerson(pVisited).EMailAdrs); end; 

AcceptVisitor memverifikasi bahwa objek yang ditransfer adalah turunan dari TPerson, karena Pengunjung hanya boleh menjalankan perintah dengan objek tersebut. Jika jenisnya cocok, perintah dieksekusi dan garis dengan properti objek ditambahkan ke bidang teks.

Tindakan pendukung untuk kesehatan kode adalah sebagai berikut. Tambahkan dua properti ke deskripsi formulir itu sendiri di bagian pribadi: FPeople tipe TPeople dan VM tipe TtiVisitorManager. Di pengendali acara pembuatan formulir, kita perlu menginisialisasi properti ini, serta mendaftarkan Pengunjung dengan perintah "show":

 FPeople := TPeople.Create; FillPeople; VM := TtiVisitorManager.Create; VM.RegisterVisitor('show',TShowVisitor); 

FilPeople juga merupakan prosedur tambahan yang mengisi daftar dengan tiga objek, kodenya diambil dari konstruktor daftar sebelumnya. Jangan lupa untuk menghancurkan semua benda yang dibuat. Dalam hal ini, kami menulis FPeople.Free dan VM.Free dalam bentuk handler penutupan.

Dan sekarang - bams! - pengendali tombol pertama:

 Memo1.Clear; VM.Execute('show',FPeople); 

Setuju, jauh lebih menyenangkan. Dan jangan bersumpah pada hash semua kelas dalam satu modul. Di akhir manual, kami akan menyapu puing-puing ini.

Langkah 9. Kelas dasar Pengunjung bekerja dengan file teks


Pada tahap ini, kita akan membuat kelas dasar Pengunjung yang tahu cara bekerja dengan file teks. Ada tiga cara untuk bekerja dengan file dalam objek pascal: prosedur lama dari waktu pascal pertama (seperti AssignFile dan ReadLn), bekerja melalui stream (TStringStream atau TFileStream), dan menggunakan objek TStringList.

Jika metode pertama sangat ketinggalan jaman, maka yang kedua dan ketiga adalah alternatif yang baik berdasarkan OOP. Pada saat yang sama, bekerja dengan stream juga memberikan manfaat seperti kemampuan mengompresi dan mengenkripsi data, tetapi membaca dan menulis baris demi baris ke aliran adalah semacam redundansi dalam contoh kami. Untuk mempermudah, kami akan memilih TStringList, yang memiliki dua metode sederhana - LoadFromFile dan SaveToFile. Tetapi ingat bahwa dengan file besar, metode ini akan melambat secara signifikan, sehingga streaming akan menjadi pilihan optimal bagi mereka.

Antarmuka kelas dasar TVisFile:

 TVisFile = class(TVisitor) protected   FList: TStringList;   FFileName: TFileName; public   constructor Create; virtual;   destructor Destroy; override; end; 

Dan implementasi konstruktor dan destruktor:

 constructor TVisFile.Create; begin inherited Create; FList := TStringList.Create; if FileExists(FFileName) then   FList.LoadFromFile(FFileName); end; destructor TVisFile.Destroy; begin FList.SaveToFile(FFileName); FList.Free; inherited; end; 

Nilai properti FFileName akan ditetapkan dalam konstruktor dari keturunan kelas dasar ini (hanya saja jangan menggunakan hardcoding, yang akan kami atur di sini, sebagai gaya pemrograman utama setelah!). Diagram kelas Pengunjung yang bekerja dengan file adalah sebagai berikut:



Sesuai dengan diagram di bawah ini, kami membuat dua turunan dari kelas dasar TVisFile: TVisTXTFile dan TVisCSVFile. Satu akan bekerja dengan file * .csv di mana bidang data dipisahkan oleh simbol (koma), yang kedua - dengan file teks di mana masing-masing bidang data akan memiliki panjang tetap per baris. Untuk kelas-kelas ini, kami hanya mendefinisikan ulang konstruktor sebagai berikut:

 constructor TVisCSVFile.Create; begin FFileName := 'contacts.csv'; inherited Create; end; constructor TVisTXTFile.Create; begin FFileName := 'contacts.txt'; inherited Create; end. 

Langkah 10. Tambahkan Pengunjung-penangan file teks


Di sini kita akan menambahkan dua Pengunjung spesifik, satu akan membaca file teks, yang kedua akan menulis untuk itu. Pengunjung yang membaca harus mengganti metode kelas dasar AcceptVisitor dan Execute. AcceptVisitor memverifikasi bahwa objek kelas TPeople diteruskan ke Pengunjung:

 Result := pVisited is TPeople; 

Eksekusi implementasi adalah sebagai berikut:

 procedure TVisTXtRead.Execute(pVisited: TVisited); var i: integer; lData: TPerson; begin if not AcceptVisitor(pVisited) then   Exit; //==> TPeople(pVisited).Clear; for i := 0 to FList.Count - 1 do begin   lData := TPerson.Create;   lData.Name := Trim(Copy(FList.Strings[i], 1, 20));   lData.EMailAdrs := Trim(Copy(FList.Strings[i], 21, 80));   TPeople(pVisited).Add(lData); end; end; 

Pengunjung pertama-tama membersihkan daftar objek TPeople yang diteruskan kepadanya dengan parameter, lalu membaca garis-garis dari objek TStringList-nya, ke mana isi file dimuat, membuat objek TPerson di setiap baris dan menambahkannya ke daftar kontainer TPeople. Untuk kesederhanaan, properti nama dan emailadrs dalam file teks dipisahkan oleh spasi.

Pengunjung catatan mengimplementasikan operasi terbalik. Konstruktornya (diganti) membersihkan TStringList internal (mis., Melakukan operasi FList.Clear; wajib setelah diwariskan), AcceptVisitor memeriksa bahwa objek kelas TPerson dilewatkan, yang bukan merupakan kesalahan, tetapi perbedaan penting dari metode membaca Pengunjung yang sama. Tampaknya lebih mudah untuk mengimplementasikan rekaman dengan cara yang sama - memindai semua objek kontainer, menambahkannya ke StringList dan kemudian menyimpannya ke file. Semua ini terjadi jika kita benar-benar berbicara tentang penulisan akhir data ke file, namun kami berencana untuk memetakan data ke database relasional, ini harus diingat. Dan dalam hal ini, kita harus menjalankan kode SQL hanya untuk objek-objek yang telah diubah (dibuat, dihapus atau diedit). Itu sebabnya sebelum Pengunjung melakukan operasi pada objek,ia harus memeriksa korespondensi tipenya:

 Result := pVisited is Tperson; 

Metode eksekusi hanya menambah StringList internal string yang diformat dengan aturan yang ditentukan: pertama, isi properti nama objek yang diteruskan, diisi dengan spasi hingga 20 karakter, kemudian isi properti emaiadrs:

 procedure TVisTXTSave.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then   exit; FList.Add(PadRight(TPerson(pVisited).Name,20)+PadRight(TPerson(pVisited).EMailAdrs,60)); end; 

Langkah 11. Tambahkan Pengunjung-penangan file CSV


Pengunjung membaca dan menulis serupa di hampir semua kolega mereka dari kelas TXT, kecuali cara memformat baris terakhir file: dalam standar CSV, nilai properti dipisahkan oleh koma. Untuk membaca baris dan mem-parsingnya ke dalam properti, kami menggunakan fungsi ExtractDelimited dari modul strutils, dan penulisan dilakukan dengan hanya menyatukan baris:

 procedure TVisCSVRead.Execute(pVisited: TVisited); var i: integer; lData: TPerson; begin if not AcceptVisitor(pVisited) then   exit; TPeople(pVisited).Clear; for i := 0 to FList.Count - 1 do begin   lData := TPerson.Create;   lData.Name := ExtractDelimited(1, FList.Strings[i], [',']);   lData.EMailAdrs := ExtractDelimited(2, FList.Strings[i], [',']);   TPeople(pVisited).Add(lData); end; end; procedure TVisCSVSave.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then   exit; FList.Add(TPerson(pVisited).Name + ',' + TPerson(pVisited).EMailAdrs); end; 

Yang tersisa bagi kami adalah mendaftarkan Pengunjung baru di Manajer dan memeriksa pengoperasian aplikasi. Di handler pembuatan formulir, tambahkan kode berikut:

 VM.RegisterVisitor('readTXT', TVisTXTRead); VM.RegisterVisitor('saveTXT',TVisTXTSave); VM.RegisterVisitor('readCSV',TVisCSVRead); VM.RegisterVisitor('saveCSV',TVisCSVSave); 

Dok tombol yang diperlukan pada formulir dan berikan penangan yang sesuai:



 procedure TForm1.ReadCSVbtnClick(Sender: TObject); begin VM.Execute('readCSV', FPeople); end; procedure TForm1.ReadTXTbtnClick(Sender: TObject); begin VM.Execute('readTXT', FPeople); end; procedure TForm1.SaveCSVbtnClick(Sender: TObject); begin VM.Execute('saveCSV', FPeople); end; procedure TForm1.SaveTXTbtnClick(Sender: TObject); begin VM.Execute('saveTXT', FPeople); end; 

Format file tambahan untuk menyimpan data dilaksanakan dengan hanya menambahkan Pengunjung yang sesuai dan mendaftarkannya di Manajer. Dan perhatikan hal-hal berikut: kami sengaja memberi nama perintah berbeda, yaitu saveTXT dan saveCSV. Jika kedua Pengunjung cocok dengan satu perintah simpan, maka keduanya akan mulai pada perintah yang sama, periksa sendiri.

Langkah 12. Pembersihan kode akhir


Untuk keindahan dan kemurnian kode yang lebih besar, serta untuk mempersiapkan proyek untuk pengembangan interaksi lebih lanjut dengan DBMS, kami akan mendistribusikan kelas-kelas kami ke dalam modul yang berbeda sesuai dengan logika dan tujuannya. Pada akhirnya, kita harus memiliki struktur modul berikut di folder proyek, yang memungkinkan kita untuk melakukan tanpa hubungan melingkar di antara mereka (ketika merakit sendiri, mengatur modul yang diperlukan dalam bagian penggunaan):

Modul
Fungsi
Kelas
tivisitor.pas
Kelas dasar template Pengunjung dan Manajer
TVisitor
TVisited
TVisMapping
TtiVisitorManager
tiobject.pas
Kelas Logika Bisnis Dasar
TtiObject
TtiObjectList
people_BOM.pas
Kelas Logika Bisnis Tertentu
TPerson
TPeople
people_SRV.pas
Kelas beton bertanggung jawab atas interaksi
TVisFile
TVisTXTFile
TVisCSVFile
TVisCSVSimpan
TVisCSVBaca
TVisTXTSave
TVisTXBaca

Kesimpulan


Pada artikel ini, kami memeriksa masalah iterasi pada koleksi atau daftar objek yang dapat memiliki tipe berbeda. Kami menggunakan templat Pengunjung yang diusulkan oleh GoF untuk secara optimal mengimplementasikan dua cara berbeda memetakan data dari objek ke file dengan format berbeda. Pada saat yang sama, berbagai metode dapat dilakukan oleh satu tim karena pembuatan Manajer Pengunjung. Pada akhirnya, contoh-contoh sederhana dan ilustratif yang dibahas dalam artikel akan membantu kita mengembangkan sistem serupa untuk memetakan objek ke basis data relasional.

Arsipkan dengan kode sumber contoh - di sini

Source: https://habr.com/ru/post/id441266/


All Articles