Bu mimaride OOP uygulamasını kırıyor muyum?


23

Bir web uygulamam var. Teknolojinin önemli olduğuna inanmıyorum. Yapı, soldaki resimde gösterilen N-katmanlı bir uygulamadır. 3 katman var.

UI (MVC modeli), İş Mantığı Katmanı (BLL) ve Veri Erişim Katmanı (DAL)

Sahip olduğum sorun BLL'imdeki uygulama olayları çağrısında mantığı ve yolları olduğu için büyük.

Uygulamadan geçen tipik bir akış şu olabilir:

Kullanıcı arabiriminde başlatılan olay, BLL'deki bir yönteme göre hareket eder, mantık gerçekleştirir (muhtemelen BLL'nin birden çok yerinde), sonunda DAL'ye, BLL'ye geri dönün (büyük olasılıkla daha fazla mantık) ve ardından UI'ya bir değer döndürür.

Bu örnekteki BLL çok meşgul ve bunun nasıl bölüneceğini düşünüyorum. Ayrıca sevmediğim bir mantık ve nesneler var.

görüntü tanımını buraya girin

Sağdaki sürüm benim çabam.

Mantık hala uygulamanın UI ve DAL arasında nasıl aktığını gösterir, ancak büyük olasılıkla hiçbir özelliği yoktur ... Yalnızca yöntemler (bu katmandaki sınıfların çoğu, herhangi bir durumu saklamadıkları için muhtemelen statik olabilir ). Poco katmanı, özellikleri olan sınıfların bulunduğu yerdir (örneğin ad, yaş, boy vb. Olan bir Person sınıfı gibi). Bunların uygulamanın akışı ile ilgisi yoktur, sadece depolarlar.

Akış olabilir:

Hatta UI'den tetiklenir ve bazı verileri UI katman denetleyicisine (MVC) iletir. Bu ham verileri çevirir ve poco modeline dönüştürür. Poco modeli daha sonra Mantıksal katmana (BLL idi) ve sonunda potansiyel olarak yol üzerinde manipüle edilen komut sorgu katmanına geçirilir. Komut sorgu katmanı, POCO'yu bir veritabanı nesnesine dönüştürür (bu neredeyse aynı şeydir, ancak biri kalıcı olmak için, diğeri ön uç için tasarlanmıştır). Öğe depolanır ve bir veritabanı nesnesi Komut Sorgu katmanına döndürülür. Daha sonra bir POCO'ya dönüştürülür, burada Mantık katmanına geri döner, potansiyel olarak daha fazla işlenir ve daha sonra UI'ye geri döner

Paylaşılan mantık ve arayüzler, MaxNumberOf_X ve TotalAllowed_X ve tüm arayüzler gibi kalıcı verilere sahip olabileceğimiz yerdir.

Hem paylaşılan mantık / arayüzler hem de DAL, mimarinin "tabanı" dır. Bunlar dış dünya hakkında hiçbir şey bilmiyor.

Her şey paylaşılan mantık / arayüzler ve DAL dışındaki poco'yu bilir.

Akış, ilk örneğe hala çok benzer, ancak her bir katmanı 1 şeyden daha fazla sorumlu kılıyor (durum, akış veya başka bir şey olabilir) ... ama bu yaklaşımla OOP'u kıracağım mı?

Mantık ve Poco'yu tanıtmak için bir örnek olabilir:

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;  
     }
}

Açıkladığınız gibi mimarinizde temel olarak yanlış bir şey görmüyorum.
Robert Harvey

19
Daha fazla bilgi vermek için kod örneğinizde yeterli işlevsel ayrıntı yok. Foobar örnekleri nadiren yeterli gösterim sağlar.
Robert Harvey


4
O yüzden bu soru için daha iyi bir başlık bulabilir olabilir çevrimiçi daha kolay bulunabilir?
Soner Gönül

1
Sadece bilgili olmak için: bir katman ve bir katman aynı şey değildir. Bir "katman" konuşlandırma, mantık hakkında bir "katman" hakkında konuşuyor. Veri katmanınız hem sunucu tarafı koduna hem de veritabanı katmanlarına dağıtılacaktır. UI katmanınız hem web istemcisi hem de sunucu tarafı kod katmanlarına dağıtılır. Gösterdiğiniz mimari 3 katmanlı bir mimaridir. Katmanlarınız "Web istemcisi", "Sunucu tarafı kodu" ve "Veritabanı" dır.
Laurent LA RIZZA

Yanıtlar:


54

Evet, temel OOP kavramlarını kırmanız çok muhtemeldir . Ancak kendini kötü hissetme, insanlar bunu her zaman yapar, bu senin mimarisinin "yanlış" olduğu anlamına gelmez. Muhtemelen uygun bir OO tasarımından daha az bakımsız olduğunu söyleyebilirim, ama bu zaten öznel değil, yine de sorunuz değil. ( İşte genel olarak üst düzey mimariyi eleştiren bir makale.)

Sebep : OOP'un en temel konsepti, veri ve mantığın tek bir birim (bir nesne) oluşturmasıdır. Her ne kadar bu çok basit ve mekanik bir ifade olsa da, tasarımınızda gerçekten takip edilmiyor (sizi doğru anlarsam). Verilerin çoğunu mantığın çoğundan açıkça ayırıyorsunuz. Vatansız (statik benzeri) yöntemlere sahip olmak, örneğin "prosedürler" olarak adlandırılır ve genellikle OOP'ye karşı antitetiktir.

Elbette her zaman istisnalar vardır, ancak bu tasarım bu şeyleri kural olarak ihlal ediyor.

Yine, "OOP ihlal ediyor"! = "Yanlış" vurguyu vurgulamak istiyorum, bu nedenle bu bir değer yargısı değildir. Bunların hepsi mimari kısıtlamalarınıza, bakım yapılabilir kullanım durumlarına, gereksinimlere vb. Bağlıdır.


9
Olumlu bir oyum var, bu iyi bir cevap, eğer kendim yazıyorsam, bunu kopyalayıp yapıştırırım, ama şunu da eklerim, eğer kendi kendinize OOP kodu yazmıyorsanız, belki de OOP olmayan bir dil düşünmelisiniz. kullanmadığınız takdirde yapamayacağınız bir sürü ek yük gelir
TheCatWhisperer

2
@ TheCatWhisperer: Modern kurumsal mimariler, OOP'yi tamamen seçici bir şekilde (örneğin DTO'lar için) atmazlar.
Robert Harvey,

@RobertHarvey Anlaşıldı, demek istediğim,
OOP'ı

C # gibi bir OOP içinde avantajların birçok dilin oop kısmında ama vb kütüphaneler, visual studio, hafıza yönetimi gibi mevcut destek ille değildir @TheCatWhisperer

@Orangesandlemons Dışarıda pek çok başka iyi desteklenen dil olduğundan eminim ...
TheCatWhisperer

31

Fonksiyonel Programlamanın temel ilkelerinden biri de saf fonksiyonlardır.

Nesne Yönelimli Programlamanın temel ilkelerinden biri, üzerinde çalıştıkları verilerle işlevleri bir araya getirmektir.

Bu temel ilkelerin her ikisi de, başvurunuzun dış dünyayla iletişim kurması gerektiğinde ortadan kalkar. Aslında, sisteminizde özel olarak hazırlanmış bir alanda bu ideallere sadık kalabilirsiniz. Kodunuzun her satırı bu ideallere uymamalıdır. Ancak, kodunuzun hiçbir satırı bu idealleri karşılamazsa, gerçekten OOP veya FP kullandığını iddia edemezsiniz.

Bu yüzden, yalnızca kaçtığınız "nesneler" olması sorun değil, çünkü ilgilendiğiniz kodu taşımak için yeniden düzenleyemeyeceğiniz bir sınırı geçmeleri gerekiyor. Sadece OOP olmadığını biliyorum. Bu gerçek. OOP, bir kez bu sınırın içinde o verilere etki eden tüm mantığı bir yerde topladığınız zamandır.

Bunu da yapmak zorunda değilsin. OOP her insan için her şey değildir. Neyse ne. Sadece bir şey olmadığında OOP'yi takip etme iddiasında bulunmayın veya kodunuzu korumaya çalışan kişilerin kafasını karıştıracaksınız.

POCO'larınızın iş mantığı var gibi görünüyor, bu yüzden anemik olmaktan çok fazla endişe etmem. Beni endişelendiren, hepsinin çok değişken göründüğü. Alıcıların ve ayarlayıcıların gerçek bir kapsülleme sağlamadığını unutmayın. POCO'nuz bu sınırlara yöneliyorsa, sorun değil. Sadece bunun size gerçek bir kapsüllenmiş OOP nesnesinin tüm faydalarını sağlamadığını anlayın. Bazıları buna Veri Aktarım Nesnesi veya DTO diyor.

Başarıyla kullandığım bir püf noktası DTO'ları yiyen OOP nesnelerini yapmak. DTO'yu parametre nesnesi olarak kullanıyorum . Yapıcım ondan durumu okuyor ( savunma kopyası olarak okunur ) ve bir kenara atıyor. Şimdi DTO'nun tamamen kapsüllenmiş ve değişmez bir versiyonuna sahibim. Bu verilerle ilgili tüm yöntemler, sınırın bu tarafında olmaları şartıyla buraya taşınabilir.

Alıcı ya da ayarlayıcı vermiyorum. Ben takip ediyorum söyle, sorma . Sen benim yöntemlerimi çağırıyorsun ve gitmeleri gerekeni yapıyorlar. Muhtemelen ne yaptıklarını bile söylemiyorlar. Sadece yapıyorlar.

Şimdi nihayetinde bir şey, bir yerlerde başka bir sınırla karşılaşacak ve bu yeniden dağılıyor. Bu iyi. Başka bir DTO daha döndürün ve duvarın üzerinden fırlatın.

Limanların ve adaptörler mimarisinin neyle ilgili olduğunun özü budur. İşlevsel bir bakış açısıyla okudum . Belki senin de ilgini çeker.


5
alıcılar ve ayarcılar gerçek bir kapsülleme sağlamaz ” - evet!
Örümcek Boris,

3
@BoristheSpider - alıcılar ve ayarlayıcılar kesinlikle kapsülleme sağlar, sadece dar kapsülleme tanımınıza uymazlar.
Davor Ždralo

4
@ DavorŽdralo: Geçici bir çözüm olarak zaman zaman kullanışlıdırlar, ancak doğaları gereği alıcılar ve alıcılar kapsüllemeyi bozar. İçsel bir değişkeni elde etmenin ve ayarlamanın bir yolunu sağlamak, kendi durumunuzdan ve ona göre hareket etmekten sorumlu olmanın zıttıdır.
cHao

5
@cHao - Bir alıcının ne olduğunu anlamıyorsun. Bir nesne özelliğinin değerini döndüren bir yöntem anlamına gelmez. Bu yaygın bir uygulamadır, ancak veritabanından bir değer döndürebilir, http üzerinden talep edebilir, anında hesaplayabilir. Dediğim gibi, alıcılar ve belirleyiciler, yalnızca insanlar kendi dar (ve yanlış) tanımlarını kullandıklarında kapsüllemeyi kırarlar.
Davor Ždralo

4
@cHao - encapsulation, uygulamanın gizlendiğini gösterir. Kapsüllenen şey budur. Bir Square sınıfında "getSurfaceArea ()" alıcıya sahipseniz, yüzey alanının bir alan olup olmadığını bilmiyorsunuz, eğer hesaplanırsa (dönüş yüksekliği * genişlik) veya üçüncü bir yöntem kullanıyorsanız, iç uygulamayı değiştirebilirsiniz ne zaman istersen, çünkü kapsüllenmiş.
Davor Ždralo

1

Açıklamanızı doğru okuduysam nesneleriniz biraz şuna benziyor: (bağlamsız zor)

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
}

Poco sınıflarınız yalnızca veri içerdiğinden, Logic sınıflarınız bu veriler üzerinde etkili olan yöntemleri içerir; evet, "Klasik OOP" ilkelerini kırdınız

Yine genelleştirilmiş açıklamanızdan söylemek zor, ancak yazdıklarınızın Anemik Alan Modeli olarak sınıflandırılması tehlikesiyle karşı karşıyayım.

Bunun özellikle kötü bir yaklaşım olduğunu düşünmüyorum, ya da Poco'nuzu yapısal olarak kabul ederseniz OOP'yi daha belirgin bir şekilde bozmaz. Bunun için Nesneleriniz artık LogicClasses. Gerçekten de, Pocos'unuzu değişmez kılarsanız, tasarım oldukça İşlevsel olarak kabul edilebilir.

Ancak, Paylaşılan Mantığa başvurduğunuzda, hemen hemen aynı olan ama aynı olmayan statik olan Pocos, tasarımınızın detayı için endişelenmeye başlıyorum.


Yazımı ekledim, esasen örneğinizi kopyaladım. Üzgünüz ti ile başlamak için net değildi
MyDaftQuestions

1
Demek istediğim, uygulamanın ne yaptığını bize söylerseniz, örnekler yazmak daha kolay olacaktır. LogicClass yerine PaymentProvider veya her neyse
Ewan

1

Tasarımınızda gördüğüm olası bir sorun (ve çok yaygın) - karşılaştığım en kötü "OO" kodlarından bazıları, "Data" nesnelerini "Kod" nesnelerinden ayıran bir mimariden kaynaklanıyordu. Bu kabus seviyesindeki şeyler! Sorun şu ki, işletme kodunuzun her yerinde, veri nesnelerinize erişmek istediğinizde, sadece satır içi kodunu girme eğilimindesiniz (Satır yapmak zorunda değilsiniz, bunu yapmak için bir yardımcı program sınıfı ya da başka bir işlev oluşturabilirsiniz. Zamanla tekrarlanan bir şey gördüm).

Erişim / güncelleme kodu genellikle toplanmaz, bu nedenle her yerde yinelenen işlevsellik elde edersiniz.

Öte yandan, bu veri nesneleri, örneğin veri tabanı kalıcılığı olarak kullanışlıdır. Üç çözüm denedim:

Değerleri "real" nesnelere içeri ve dışarı kopyalamak ve veri nesnenizi atmak zahmetlidir (ancak bu şekilde gitmek istiyorsanız geçerli bir çözüm olabilir).

Veri nesnelerine veri wrangling yöntemleri eklemek işe yarayabilir, ancak birden fazla şey yapan büyük dağınık bir veri nesnesi için yapabilir. Ayrıca, birçok kalıcılık mekanizması kamu erişimcileri istediğinden, kapsüllemeyi daha da zorlaştırabilir ... Yaptığımda sevmedim ama geçerli bir çözüm

Benim için en iyi olan çözüm, "Data" sınıfını kapsayan ve tüm veri wrangling işlevlerini içeren bir "Wrapper" sınıfı kavramıdır - o zaman veri sınıfını hiç göstermiyorum (Hatta ayarlayıcılar ve alıcılar bile değil) kesinlikle gerekli olmadıkça). Bu, nesneyi doğrudan işleme eğilimini ortadan kaldırır ve sarıcıya ortak işlevsellik eklemeye zorlar.

Diğer bir avantajı, veri sınıfınızın her zaman geçerli bir durumda olduğundan emin olmanızdır. İşte hızlı bir psuedocode örneği:

// 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) 
    }
}

Yaş kontrolü kodunuzun farklı alanlara yayıldığını ve ayrıca kullanmak istemediğinizi unutmayın; çünkü doğum gününün ne olduğunu bile öğrenemezsiniz (başka bir şeye ihtiyacınız olmadıkça, hangi davaya ekleyebilirsiniz).

Ben sadece veri nesnesini genişletmeme eğilimindeyim çünkü bu kapsüllemeyi ve emniyet garantisini kaybedersiniz - bu noktada sadece veri sınıfına yöntemleri ekleyebilirsiniz.

Bu şekilde, iş mantığınız, etrafa yayılmış çok sayıda veri erişim önemsizine / yineleyicisine sahip değildir, çok daha okunaklı ve daha az gereksiz hale gelir. Ayrıca, aynı sebeple koleksiyonları her zaman sarmalama alışkanlığına girmeyi tavsiye ediyorum - döngüleri / arama yapılarını iş mantığınızdan uzak tutmak ve her zaman iyi durumda olmalarını sağlamak.


1

Kodunuzu asla değiştirmeyin, çünkü sizce ya da birileri size bunun olmadığını ya da olmadığını söyler. Size sorun çıkarırsa kodunuzu değiştirin ve başkaları oluşturmadan bu sorunlardan kaçınmanın bir yolunu buldunuz.

Bundan başka, şeyleri beğenmemek dışında, bir değişiklik yapmak için çok fazla zaman harcamak istersiniz. Şu an sahip olduğun problemleri yaz. Yeni tasarımınızın sorunları nasıl çözeceğini yazın. İyileştirmenin değerini ve değişikliklerinizi yapmanın maliyetini belirleyin. O zaman - ve bu en önemlisi - bu değişiklikleri tamamlamak için zamanınız olduğundan emin olun, yoksa bu durumda yarıya, bu durumda yarıya, ve bu mümkün olan en kötü durumdur. (Bir keresinde 13 farklı tipte ve bir tipte standardize etmek için üç adet tanımlanabilir yarı-boy çaba içeren bir proje üzerinde çalıştım.)


0

“OOP” kategorisi, tanımladığınızdan çok daha büyük ve daha soyut. Tüm bunları umursamıyor. Net sorumluluk, uyum ve eşleşmeyi önemser. Dolayısıyla, sorduğunuz seviyede, "OOPS uygulaması" hakkında soru sormanız pek mantıklı gelmiyor.

Senin örneğine şöyle dedi:

Bana öyle geliyor ki, MVC'nin ne anlama geldiğiyle ilgili bir yanlış anlaşılma var. Kullanıcı arayüzü "MVC" yi, iş mantığınızdan ve "arka uç" kontrolünüzden ayrı olarak çağırıyorsunuz. Ancak benim için MVC tüm web uygulamasını içeriyor:

  • Model - işletme verisi + mantığı içerir
    • Modelin uygulama detayı olarak veri katmanı
  • Görünüm - Kullanıcı arayüzü kodu, HTML şablonları, CSS vb.
    • JavaScript gibi müşteri tarafı yönlerini veya "tek sayfalık" web uygulamaları vb. Kütüphanelerini içerir.
  • Kontrol - diğer tüm parçalar arasında sunucu tarafı tutkalı
  • (Burada girmeyeceğim ViewModel, Batch vb. Uzantılar var)

Burada aşırı derecede önemli bazı temel varsayımlar var:

  • Model sınıfı / nesneler hiçbir zaman diğer bölümlerin hiçbiriyle ilgili hiçbir bilgiye sahip değildir (Görünüm, Kontrol, ...). Onları asla çağırmaz, onlar tarafından çağrıldığını varsaymaz, hiçbir sesssion niteliği / parametresi veya bu satır boyunca başka bir şey alamaz. Tamamen yalnız. Bunu destekleyen dillerde (örn. Ruby), manuel bir komut satırı çalıştırabilir, Model sınıflarını başlatabilir, kalp içeriğinizle onlarla çalışabilir ve yaptıkları her şeyi herhangi bir Kontrol veya Görünüm örneği veya başka bir kategori olmadan yapabilirsiniz. En önemlisi, oturumlar, kullanıcılar vb. Hakkında bilgisi yoktur.
  • Bir model dışında veri katmanına hiçbir şey dokunmuyor.
  • Görünüm, yalnızca modele hafifçe dokunuyor (malzeme görüntüleniyor vb.) Ve başka bir şey yok. (İyi bir uzantının, verileri karmaşık bir şekilde işlemek için daha büyük işlem yapan özel sınıflar olan "ViewModel" olduğunu unutmayın; bu, Model veya Görünüm'e iyi bir şekilde uymaz - bu, şişkinliği gidermek / önlemek için iyi bir adaydır. saf Model).
  • Kontrol, olabildiğince hafiftir, ancak diğer tüm oyuncuları bir araya getirmek ve aralarında bir şeyler aktarmaktan sorumludur (yani, kullanıcı girişlerini bir formdan çıkarıp modele iletme, istisnaları iş mantığından yararlı bir duruma getirme) kullanıcı için hata mesajları, vb.) Web / HTTP / REST API'leri vb. İçin tüm yetkilendirme, güvenlik, oturum yönetimi, kullanıcı yönetimi vb. Burada (ve sadece burada) gerçekleşir.

Önemli: UI, MVC'nin bir parçasıdır . Diğer yuvarlama değil (diyagramınızdaki gibi). Bunu benimsemişseniz, o zaman şişman modeller aslında oldukça iyi - gerçekten yapmaması gereken şeyleri içermemesi koşuluyla.

"Şişman modeller" in tüm işletme mantığının Model kategorisinde olduğu anlamına gelir (paket, modül, seçtiğiniz dilde adı ne olursa olsun). Bireysel sınıflar, açıkça kendiniz verdiğiniz kodlama kuralları ne olursa olsun iyi bir şekilde OOP tarafından yapılandırılmalıdır (yani, sınıf başına veya her yöntem için azami kod satırı).

Ayrıca, veri katmanının nasıl uygulandığının çok önemli sonuçları olduğuna dikkat edin; özellikle model katmanının bir veri katmanı olmadan çalışıp çalışamayacağı (örneğin, birim testi için veya pahalı Oracle DB'ler yerine geliştirici dizüstü bilgisayarındaki ucuz bellekteki DB'ler veya ne varsa). Fakat bu gerçekten şu an baktığımız mimarlık düzeyinde bir uygulama detayı. Açıkçası burada hala bir ayrılığa sahip olmak istiyorsunuz, yani, doğrudan yoğun bir şekilde birbirine bağlanan, veri erişimiyle doğrudan birleştirilen saf alan mantığına sahip olan kodu görmek istemem. Başka bir soru için bir konu.

Sorunuza geri dönebilmek için: Bana göre yeni mimariniz ve tanımladığım MVC planı arasında büyük bir örtüşme var, bu yüzden tamamen yanlış bir yolda değilsiniz, ya da bazı şeyleri yeniden keşfediyor gibi görünüyorsunuz. veya şu anki programlama ortamınız / kütüphanelerinizin bunu önerdiği için kullanabilirsiniz. Bana söylemek zor. Bu yüzden, neyi hedeflediğinizin özellikle iyi veya kötü olup olmadığı konusunda kesin bir cevap veremem. Her bir "şeyin" bundan tamamen sorumlu bir sınıfa sahip olup olmadığını kontrol ederek öğrenebilirsiniz; Her şeyin çok yapışkan olup, düşük eşleşmiş olup olmadığı. Bu size iyi bir gösterge verir ve bence, iyi bir OOP tasarımı için yeterli (veya isterseniz iyi bir kıyaslama noktasıdır).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.