Apakah saya melanggar praktek OOP dengan arsitektur ini?

23

Saya punya aplikasi web. Saya tidak percaya teknologi itu penting. Strukturnya adalah aplikasi tingkat-N, ditunjukkan pada gambar di sebelah kiri. Ada 3 lapisan.

UI (pola MVC), Lapisan Logika Bisnis (BLL) dan Lapisan Akses Data (DAL)

Masalah yang saya miliki adalah BLL saya sangat besar karena memiliki logika dan jalur melalui panggilan peristiwa aplikasi.

Aliran khas melalui aplikasi dapat:

Acara dipecat di UI, melintasi metode di BLL, melakukan logika (mungkin di beberapa bagian BLL), akhirnya ke DAL, kembali ke BLL (di mana kemungkinan lebih banyak logika) dan kemudian mengembalikan beberapa nilai ke UI.

BLL dalam contoh ini sangat sibuk dan saya berpikir bagaimana membagi ini. Saya juga memiliki logika dan objek yang digabungkan yang tidak saya sukai.

masukkan deskripsi gambar di sini

Versi di sebelah kanan adalah usaha saya.

Logika ini masih bagaimana aplikasi mengalir antara UI dan DAL, tetapi ada kemungkinan ada sifat ... Hanya metode (mayoritas kelas dalam lapisan ini bisa mungkin statis karena mereka tidak menyimpan setiap negara). Lapisan Poco adalah tempat kelas yang memiliki properti (seperti kelas Orang di mana akan ada nama, usia, tinggi dll). Ini tidak ada hubungannya dengan aliran aplikasi, mereka hanya menyimpan keadaan.

Alurnya bisa:

Bahkan dipicu dari UI dan melewatkan beberapa data ke UI layer controller (MVC). Ini menerjemahkan data mentah dan mengubahnya menjadi model poco. Model poco kemudian diteruskan ke lapisan Logic (yang merupakan BLL) dan akhirnya ke lapisan permintaan perintah, berpotensi dimanipulasi di jalan. Lapisan query perintah mengkonversi POCO ke objek database (yang hampir sama, tetapi satu dirancang untuk kegigihan, yang lain untuk ujung depan). Item disimpan dan objek database dikembalikan ke lapisan Command Query. Ini kemudian dikonversi menjadi POCO, di mana ia kembali ke lapisan Logic, berpotensi diproses lebih lanjut dan akhirnya, kembali ke UI

Logika dan antarmuka bersama adalah tempat kami mungkin memiliki data persisten, seperti MaxNumberOf_X dan TotalAllowed_X dan semua antarmuka.

Baik logika / antarmuka bersama dan DAL adalah "basis" dari arsitektur. Ini tidak tahu apa-apa tentang dunia luar.

Semuanya tahu tentang poco selain logika / antarmuka bersama dan DAL.

Alurnya masih sangat mirip dengan contoh pertama, tetapi itu membuat setiap lapisan lebih bertanggung jawab untuk 1 hal (baik itu keadaan, aliran atau apa pun) ... tetapi apakah saya melanggar OOP dengan pendekatan ini?

Contoh untuk memperagakan Logika dan Poco dapat berupa:

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method1(PocoB pocoB) 
    { 
        return cmdQuery.Save(pocoB); 
    }

    /*This has no state objects, only ways to communicate with other 
    layers such as the cmdQuery. Everything else is just function 
    calls to allow flow via the program */
    public PocoA Method2(PocoB pocoB) 
    {         
        pocoB.UpdateState("world"); 
        return Method1(pocoB);
    }

}

public struct PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     public int DataC {get;set;}

    /*This simply returns something that is part of this class. 
     Everything is self-contained to this class. It doesn't call 
     trying to directly communicate with databases etc*/
     public int GetValue()
     {

         return DataB * DataC; 
     }

     /*This simply sets something that is part of this class. 
     Everything is self-contained to this class. 
     It doesn't call trying to directly communicate with databases etc*/
     public void UpdateState(string input)
     {        
         DataA += input;  
     }
}
MyDaftQuestions
sumber
Saya tidak melihat ada kesalahan mendasar pada arsitektur Anda seperti yang saat ini Anda gambarkan.
Robert Harvey
19
Tidak ada detail fungsional yang cukup dalam contoh kode Anda untuk memberikan wawasan lebih lanjut. Contoh-contoh Foobar jarang memberikan ilustrasi yang cukup.
Robert Harvey
1
Diserahkan untuk pertimbangan Anda: Baruco 2012
Theraot
4
Bisakah kita menemukan judul yang lebih baik untuk pertanyaan ini sehingga dapat ditemukan online lebih mudah?
Soner Gönül
1
Hanya untuk menjadi bertele-tele: tingkat dan lapisan bukanlah hal yang sama. "Tingkat" berbicara tentang penyebaran, "lapisan" tentang logika. Lapisan data Anda akan digunakan untuk tingkatan-sisi-kode-server dan basis data. Lapisan UI Anda akan digunakan untuk tingkatan kode sisi-klien-web dan server. Arsitektur yang Anda tampilkan adalah arsitektur 3-lapisan. Tingkatan Anda adalah "Klien web", "Kode sisi server" dan "Database".
Laurent LA RIZZA

Jawaban:

54

Ya, Anda sangat mungkin melanggar konsep inti OOP. Namun jangan merasa buruk, orang melakukan ini sepanjang waktu, itu tidak berarti bahwa arsitektur Anda "salah". Saya akan mengatakan itu mungkin kurang dipelihara daripada desain OO yang tepat, tapi ini agak subyektif dan bukan pertanyaan Anda. ( Ini adalah artikel saya yang mengkritik arsitektur n-tier secara umum).

Penalaran : Konsep OOP yang paling dasar adalah data dan logika membentuk satu unit (objek). Meskipun ini adalah pernyataan yang sangat sederhana dan mekanis, meskipun demikian, itu tidak benar-benar diikuti dalam desain Anda (jika saya mengerti Anda dengan benar). Anda cukup jelas memisahkan sebagian besar data dari sebagian besar logika. Memiliki metode stateless (seperti statik) misalnya disebut "prosedur", dan umumnya berlawanan dengan OOP.

Tentu saja selalu ada pengecualian, tetapi desain ini melanggar hal-hal ini sebagai aturan.

Sekali lagi, saya ingin menekankan "melanggar OOP"! = "Salah", jadi ini belum tentu penilaian nilai. Itu semua tergantung pada kendala arsitektur Anda, kasus penggunaan rawatan, persyaratan, dll.

Robert Bräutigam
sumber
9
Punya upvote, ini adalah jawaban yang bagus, jika saya menulis sendiri, saya akan menyalin & menempel ini, tetapi juga menambahkan bahwa, jika Anda menemukan diri Anda tidak menulis kode OOP, mungkin Anda harus mempertimbangkan bahasa non-OOP karena datang dengan banyak overhead tambahan yang dapat Anda lakukan tanpa jika Anda tidak menggunakannya
TheCatWhisperer
2
@TheCatWhisperer: Arsitektur perusahaan modern tidak membuang OOP sepenuhnya, hanya secara selektif (misalnya untuk DTO).
Robert Harvey
@RobertHarvey Setuju, maksud saya jika Anda hampir tidak menggunakan OOP di mana pun dalam desain Anda
TheCatWhisperer
@TheCatWhisperer banyak keuntungan dalam oop seperti c # tidak harus di bagian oop bahasa tetapi dalam dukungan yang tersedia seperti perpustakaan, visual studio, manajemen memori dll.
@Orangesandlemons Saya yakin ada banyak bahasa lain yang didukung dengan baik di luar sana ...
TheCatWhisperer
31

Salah satu prinsip inti dari Pemrograman Fungsional adalah fungsi murni.

Salah satu prinsip inti dari Pemrograman Berorientasi Objek adalah menggabungkan fungsi-fungsi dengan data yang ditindaklanjuti.

Kedua prinsip inti ini hilang ketika aplikasi Anda harus berkomunikasi dengan dunia luar. Memang Anda hanya bisa setia pada cita-cita ini dalam ruang yang disiapkan khusus dalam sistem Anda. Tidak setiap baris kode Anda harus memenuhi cita-cita ini. Tetapi jika tidak ada baris kode Anda memenuhi ideal ini, Anda tidak dapat benar-benar mengklaim menggunakan OOP atau FP.

Jadi tidak apa-apa untuk memiliki data hanya "objek" yang Anda putar-putar karena Anda membutuhkannya untuk melewati batas yang tidak dapat Anda refactor untuk memindahkan kode yang tertarik. Hanya tahu itu bukan OOP. Itu kenyataan. OOP adalah ketika, begitu di dalam batas itu Anda mengumpulkan semua logika yang bertindak pada data itu ke satu tempat.

Bukan berarti Anda harus melakukannya juga. OOP bukan segalanya bagi semua orang. Itu adalah apa adanya. Hanya saja, jangan mengklaim sesuatu mengikuti OOP ketika tidak atau Anda akan membingungkan orang yang mencoba mempertahankan kode Anda.

POCO Anda tampaknya memiliki logika bisnis di dalamnya, jadi saya tidak akan terlalu khawatir tentang menjadi anemia. Yang membuat saya khawatir adalah mereka semua tampak sangat bisa berubah. Ingat bahwa getter dan setter tidak memberikan enkapsulasi nyata. Jika POCO Anda menuju batas itu maka baik-baik saja. Hanya mengerti ini tidak memberi Anda manfaat penuh dari objek OOP enkapsulasi nyata. Beberapa orang menyebut ini Obyek Transfer Data atau DTO.

Trik yang saya gunakan dengan sukses adalah untuk membuat objek OOP yang memakan DTO. Saya menggunakan DTO sebagai objek parameter . Konstruktor saya membaca status dari itu (dibaca sebagai salinan defensif ) dan mengabaikannya. Sekarang saya memiliki versi DTO yang sepenuhnya dienkapsulasi dan tidak dapat diubah. Semua metode yang berkaitan dengan data ini dapat dipindahkan di sini asalkan mereka berada di sisi batas itu.

Saya tidak menyediakan getter atau setter. Saya ikuti katakan, jangan tanya . Anda memanggil metode saya dan mereka melakukan apa yang perlu dilakukan. Mereka bahkan mungkin tidak memberi tahu Anda apa yang mereka lakukan. Mereka hanya melakukannya.

Sekarang akhirnya sesuatu, di suatu tempat akan berjalan ke batas lain dan ini semua berantakan lagi. Tidak apa-apa. Putar DTO lain dan lemparkan ke dinding.

Inilah inti dari semua arsitektur port dan adapter. Saya sudah membacanya dari sudut pandang fungsional . Mungkin itu akan menarik minat Anda juga.

candied_orange
sumber
5
" getter dan setter tidak memberikan enkapsulasi nyata " - ya!
Boris the Spider
3
@ BoristheSpider - getter dan setter benar-benar memberikan enkapsulasi, mereka hanya tidak sesuai dengan definisi enkapsulasi Anda yang sempit.
Davor Ždralo
4
@ DavorŽdralo: Mereka kadang-kadang berguna sebagai solusi, tetapi pada dasarnya, getter dan setters merusak enkapsulasi. Menyediakan cara untuk mendapatkan dan menetapkan beberapa variabel internal adalah kebalikan dari bertanggung jawab atas kondisi Anda sendiri dan untuk bertindak atasnya.
cao
5
@ cHao - Anda tidak mengerti apa rajanya. Itu tidak berarti metode yang mengembalikan nilai properti objek. Ini adalah implementasi yang umum, tetapi dapat mengembalikan nilai dari database, memintanya melalui http, menghitungnya dengan cepat, apa pun. Seperti yang saya katakan, getter dan setter menghancurkan enkapsulasi hanya ketika orang menggunakan definisi mereka sendiri yang sempit (dan salah).
Davor Ždralo
4
@ cHao - enkapsulasi berarti Anda menyembunyikan implementasi. Itulah yang dirangkum. Jika Anda memiliki getter "getSurfaceArea ()" di kelas Square, Anda tidak tahu apakah luas permukaan adalah bidang, jika dihitung dengan cepat (mengembalikan tinggi * lebar) atau metode ketiga, sehingga Anda dapat mengubah implementasi internal kapan saja Anda suka, karena itu dikemas.
Davor Ždralo
1

Jika saya membaca penjelasan Anda dengan benar, objek Anda terlihat sedikit seperti ini: (rumit tanpa konteks)

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method(PocoB pocoB) { ... }
}

public class PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     ... etc
}

Di mana kelas Poco Anda hanya berisi data dan kelas Logika Anda berisi metode yang bertindak atas data itu; ya, Anda telah melanggar prinsip "Classic OOP"

Sekali lagi, sulit untuk mengatakan dari deskripsi umum Anda, tetapi saya akan membahayakan bahwa apa yang Anda tulis dapat dikategorikan sebagai Model Domain Anemik.

Saya tidak berpikir ini adalah pendekatan yang sangat buruk, juga, jika Anda menganggap Poco Anda sebagai struct, tidak perlu melanggar OOP dalam arti yang lebih spesifik. Karena Objek Anda sekarang adalah LogicClasses. Memang jika Anda membuat Pocos Anda tidak berubah, desain bisa dianggap cukup fungsional.

Namun, ketika Anda mereferensikan Shared Logic, Pocos yang hampir tetapi tidak sama dan statika, saya mulai khawatir tentang detail desain Anda.

Ewan
sumber
Saya telah menambahkan ke posting saya, pada dasarnya menyalin contoh Anda. Maaf, saya tidak jelas untuk memulai
MyDaftQuestions
1
yang saya maksud adalah, jika Anda memberi tahu kami aplikasi apa yang dilakukan, akan lebih mudah untuk menulis contoh. Alih-alih LogicClass Anda bisa memiliki PaymentProvider atau apa pun
Ewan
1

Satu masalah potensial yang saya lihat dalam desain Anda (dan ini sangat umum) - beberapa kode "OO" yang benar-benar terburuk yang pernah saya temui disebabkan oleh arsitektur yang memisahkan objek "Data" dari objek "Code". Ini adalah hal tingkat mimpi buruk! Masalahnya adalah bahwa di mana-mana dalam kode bisnis Anda ketika Anda ingin mengakses objek data Anda, Anda cenderung untuk hanya kode di sana inline (Anda tidak harus, Anda bisa membangun kelas utilitas atau fungsi lain untuk menanganinya tetapi ini adalah apa Saya telah melihat terjadi berulang kali dari waktu ke waktu).

Kode akses / pembaruan umumnya tidak dikumpulkan sehingga Anda berakhir dengan fungsi duplikat di mana-mana.

Di sisi lain, objek data tersebut bermanfaat, misalnya sebagai kegigihan basis data. Saya sudah mencoba tiga solusi:

Menyalin nilai masuk dan keluar ke objek "nyata" dan membuang objek data Anda membosankan (tetapi bisa menjadi solusi yang valid jika Anda ingin pergi ke sana).

Menambahkan metode pertengkaran data ke objek data dapat bekerja tetapi dapat membuat objek data berantakan besar yang melakukan lebih dari satu hal. Ini juga dapat membuat enkapsulasi lebih sulit karena banyak mekanisme kegigihan menginginkan pengakses publik ... Saya belum menyukainya ketika saya melakukannya tetapi ini adalah solusi yang valid

Solusi yang paling berhasil bagi saya adalah konsep kelas "Wrapper" yang merangkum kelas "Data" dan berisi semua fungsi pertengkaran data - maka saya tidak memaparkan kelas data sama sekali (Bahkan tidak setter dan getter) kecuali mereka benar-benar dibutuhkan). Ini menghilangkan godaan untuk memanipulasi objek secara langsung dan memaksa Anda untuk menambahkan fungsionalitas bersama ke pembungkus.

Keuntungan lainnya adalah Anda dapat memastikan bahwa kelas data Anda selalu dalam keadaan valid. Berikut ini adalah contoh kode cepat:

// Data Class
Class User {
    String name;
    Date birthday;
}

Class UserHolder {
    final private User myUser // Cannot be null or invalid

    // Quickly wrap an object after getting it from the DB
    public UserHolder(User me)
    {
        if(me == null ||me.name == null || me.age < 0)
            throw Exception
        myUser=me
    }

    // Create a new instance in code
    public UserHolder(String name, Date birthday) {
        User me=new User()
        me.name=name
        me.birthday=birthday        
        this(me)
    }
    // Methods access attributes, they try not to return them directly.
    public boolean canDrink(State state) {
        return myUser.birthday.year < Date.yearsAgo(state.drinkingAge) 
    }
}

Perhatikan bahwa Anda tidak memiliki penyebaran pemeriksaan usia di seluruh kode Anda di area yang berbeda dan juga bahwa Anda tidak tergoda untuk menggunakannya karena Anda bahkan tidak bisa mengetahui apa ulang tahun itu (kecuali jika Anda memerlukannya untuk hal lain, di yang mana Anda dapat menambahkannya).

Saya cenderung tidak hanya memperluas objek data karena Anda kehilangan enkapsulasi ini dan jaminan keamanan - pada saat itu Anda mungkin juga menambahkan metode ke kelas data.

Dengan cara itu logika bisnis Anda tidak memiliki banyak sampah akses data / iterator tersebar di seluruh itu, itu menjadi jauh lebih mudah dibaca dan kurang berlebihan. Saya juga merekomendasikan untuk membiasakan diri selalu membungkus koleksi untuk alasan yang sama - menjaga perulangan / pencarian konstruksi dari logika bisnis Anda dan memastikan mereka selalu dalam keadaan baik.

Bill K
sumber
1

Jangan pernah mengubah kode Anda karena Anda pikir atau seseorang memberi tahu Anda bahwa ini bukan ini atau bukan itu. Ubah kode Anda jika itu memberi Anda masalah dan Anda menemukan cara untuk menghindari masalah ini tanpa membuat orang lain.

Jadi selain Anda tidak menyukai hal-hal, Anda ingin menginvestasikan banyak waktu untuk melakukan perubahan. Tuliskan masalah yang Anda miliki sekarang. Tuliskan bagaimana desain baru Anda akan menyelesaikan masalah. Cari tahu nilai peningkatan dan biaya untuk melakukan perubahan Anda. Kemudian - dan ini yang paling penting - pastikan bahwa Anda memiliki waktu untuk menyelesaikan perubahan itu, atau Anda akan berakhir setengah di negara bagian ini, setengah di negara bagian itu, dan itulah situasi terburuk yang mungkin terjadi. (Saya pernah mengerjakan proyek dengan 13 jenis string yang berbeda, dan tiga upaya setengah-setengah yang dapat diidentifikasi untuk melakukan standarisasi pada satu jenis)

gnasher729
sumber
0

Kategori "OOP" jauh lebih besar dan lebih abstrak daripada yang Anda gambarkan. Itu tidak peduli dengan semua ini. Itu peduli tentang tanggung jawab yang jelas, kohesi, penggabungan Jadi pada level yang Anda tanyakan, tidak masuk akal untuk bertanya tentang "latihan OOPS".

Yang mengatakan, untuk contoh Anda:

Menurut saya ada kesalahpahaman tentang apa arti MVC. Anda memanggil UI Anda "MVC", secara terpisah dari logika bisnis dan "backend" kontrol Anda. Tetapi bagi saya, MVC mencakup seluruh aplikasi web:

  • Model - berisi data bisnis + logika
    • Lapisan data sebagai detail implementasi model
  • Lihat - kode UI, templat HTML, CSS dll.
    • Termasuk aspek sisi klien seperti JavaScript, atau pustaka untuk aplikasi web "satu halaman" dll.
  • Kontrol - lem sisi server di antara semua bagian lainnya
  • (Ada ekstensi seperti ViewModel, Batch dll. Yang tidak akan saya bahas, di sini)

Ada beberapa asumsi dasar yang sangat penting di sini:

  • Kelas Model / benda tidak pernah memiliki apa pun pengetahuan sama sekali tentang salah satu bagian lain (Lihat, Control, ...). Itu tidak pernah memanggil mereka, itu tidak menganggap dipanggil oleh mereka, tidak mendapat atribut / parameter sesssion atau apa pun di sepanjang baris ini. Benar-benar sendirian. Dalam bahasa yang mendukung ini (misalnya, Ruby), Anda dapat menjalankan baris perintah manual, instantiate kelas Model, bekerja dengan mereka untuk isi hati Anda, dan dapat melakukan semua yang mereka lakukan tanpa instance Control atau View atau kategori lainnya. Tidak memiliki pengetahuan tentang sesi, pengguna, dll., Yang paling penting.
  • Tidak ada yang menyentuh lapisan data kecuali melalui model.
  • Tampilan hanya memiliki sedikit sentuhan pada model (menampilkan barang dll.) Dan tidak ada yang lain. (Perhatikan bahwa ekstensi yang baik adalah "ViewModel" yang merupakan kelas khusus yang melakukan pemrosesan lebih besar untuk merender data dengan cara yang rumit, yang tidak akan cocok dengan Model atau Tampilan - ini adalah kandidat yang baik untuk menghapus / menghindari mengasapi di dalam Model murni).
  • Kontrol seringan mungkin, tetapi bertanggung jawab untuk mengumpulkan semua pemain lain bersama-sama, dan mentransfer barang-barang di antara mereka (yaitu, mengekstrak entri pengguna dari formulir dan meneruskannya ke model, meneruskan pengecualian dari logika bisnis ke yang berguna pesan kesalahan untuk pengguna, dll.). Untuk API Web / HTTP / REST dll., Semua otorisasi, keamanan, manajemen sesi, manajemen pengguna dll terjadi di sini (dan hanya di sini).

Yang penting: UI adalah bagian dari MVC. Bukan sebaliknya (seperti pada diagram Anda). Jika Anda menerimanya, maka model gemuk sebenarnya cukup bagus - asalkan mereka memang tidak mengandung barang-barang yang seharusnya tidak mereka miliki.

Perhatikan bahwa "model gemuk" berarti bahwa semua logika bisnis ada dalam kategori Model (paket, modul, apa pun nama dalam bahasa pilihan Anda). Kelas individual jelas harus terstruktur OOP dengan cara yang baik per pedoman pengkodean apa pun yang Anda berikan sendiri (yaitu, beberapa baris kode maksimum per kelas atau per metode, dll.).

Juga perhatikan bahwa bagaimana layer data diimplementasikan memiliki konsekuensi yang sangat penting; terutama apakah lapisan model dapat berfungsi tanpa lapisan data (misalnya, untuk pengujian unit, atau untuk DB di memori murah pada laptop pengembang, bukan Oracle DB mahal atau apa pun yang Anda miliki). Tapi ini benar-benar detail implementasi pada tingkat arsitektur yang kita lihat sekarang. Jelas di sini Anda masih ingin memiliki pemisahan, yaitu saya tidak ingin melihat kode yang memiliki logika domain murni langsung disambungkan dengan akses data, sangat menyatukan ini. Topik untuk pertanyaan lain.

Untuk kembali ke pertanyaan Anda: Menurut saya ada banyak tumpang tindih antara arsitektur baru Anda dan skema MVC yang telah saya jelaskan, jadi Anda tidak berada di jalan yang sepenuhnya salah, tetapi Anda tampaknya menciptakan kembali beberapa hal, atau menggunakannya karena lingkungan / perpustakaan pemrograman Anda saat ini menyarankan demikian. Sulit mengatakannya untukku. Jadi saya tidak bisa memberi Anda jawaban yang tepat tentang apakah yang Anda maksudkan itu baik atau buruk. Anda dapat mengetahuinya dengan memeriksa apakah setiap "benda" memiliki tepat satu kelas yang bertanggung jawab atasnya; apakah semuanya sangat kohesif dan berpasangan rendah. Itu memberi Anda indikasi yang baik, dan, menurut pendapat saya, cukup untuk desain OOP yang baik (atau tolok ukur yang sama, jika Anda mau).

AnoE
sumber