SOLID ilkelerini uygulama


13

SOLID tasarım ilkelerinde oldukça yeniyim . Sebeplerini ve faydalarını anlıyorum, ancak yine de SOLID ilkelerini kullanmak için pratik bir egzersiz olarak yeniden düzenlemek istediğim daha küçük bir projeye uygulayamıyorum. Mükemmel çalışan bir uygulamayı değiştirmeye gerek olmadığını biliyorum, ancak yine de yeniden düzenlemek istiyorum, böylece gelecekteki projeler için tasarım deneyimi kazanıyorum.

Uygulama aşağıdaki görevi (aslında çok daha fazla ama basit tutalım): Veritabanı Tablosu / Sütun / Görünüm vb tanımları içeren bir XML dosyasını okumak ve oluşturmak için kullanılabilecek bir SQL dosyası oluşturmak zorundadır ORACLE veritabanı şeması.

(Not: Lütfen neden ihtiyacım olduğunu veya neden XSLT'yi kullanmadığımı tartışmaktan kaçının, nedenleri var, ancak konu dışı.)

Başlangıç ​​olarak, yalnızca Tablolara ve Kısıtlamalara bakmayı seçtim. Sütunları görmezden gelirseniz, bunu şu şekilde bildirebilirsiniz:

Kısıtlama, tablonun bir parçasıdır (veya daha kesin olarak CREATE TABLE ifadesinin bir parçasıdır) ve bir kısıtlama başka bir tabloya da başvurabilir.

İlk olarak, uygulamanın şu anda nasıl göründüğünü açıklayacağım (SOLID uygulamadan):

Şu anda, uygulama, tablonun sahip olduğu Kısıtlamalar için bir işaretçi listesi ve bu tabloya başvuran Kısıtlar için bir işaretçi listesi içeren bir "Tablo" sınıfına sahiptir. Bir bağlantı kurulduğunda, geriye doğru bağlantı da kurulacaktır. Tablo, her bir Kısıtlamanın createStatement () işlevini çağıran bir createStatement () yöntemine sahiptir. Bahsedilen yöntem, isimlerini almak için sahip tablosuna ve referans verilen tabloya bağlantıları kullanacaktır.

Açıkçası, bu SOLID için hiç geçerli değildir. Örneğin, gerekli "ekleme" / "kaldır" yöntemleri ve bazı büyük nesne yok ediciler açısından kodu şişiren dairesel bağımlılıklar vardır.

Yani birkaç soru var:

  1. Dairesel bağımlılıkları Bağımlılık Enjeksiyonu kullanarak çözmeli miyim? Eğer öyleyse, Kısıtlama yapıcı sahibi (ve isteğe bağlı olarak başvurulan) tablo alması gerektiğini varsayalım. Ancak tek bir tablo için kısıtlamaların listesini nasıl aşabilirim?
  2. Table sınıfı hem kendi durumunu (örn. Tablo adı, tablo yorumu vb.) Hem de Kısıtlama bağlantılarını saklıyorsa, bunlar Tek Sorumluluk İlkesini düşünerek bir veya iki "sorumluluk" mudur?
  3. Durum 2 doğruysa, sadece bağlantıları yöneten mantıksal iş katmanında yeni bir sınıf oluşturmalı mıyım? Eğer öyleyse, 1. açıkçası artık alakalı olmayacaktır.
  4. "CreateStatement" yöntemleri Tablo / Kısıtlama sınıflarının parçası mı olmalı yoksa bunları da dışarı taşımalı mıyım? Eğer öyleyse, nereye? Her veri depolama sınıfı için bir Manager sınıfı (yani, Tablo, Kısıtlama, ...)? Ya da bağlantı başına bir yönetici sınıfı oluşturun (3.'ye benzer)?

Bu sorulardan birini cevaplamaya çalıştığımda kendimi bir yerlerde çemberler içinde koşarken buluyorum.

Sütunlar, endeksler vb. Eklerseniz sorun çok daha karmaşık hale gelir, ancak basit Tablo / Kısıtlama şeyiyle bana yardımcı olursanız, belki de geri kalanları kendi başıma halledebilirim.


3
Hangi dili kullanıyorsunuz? En azından iskelet kodunu gönderebilir misin? Kod kalitesini ve olası yeniden düzenlemeleri gerçek kodu görmeden tartışmak çok zordur.
Péter Török

Ben C ++ kullanıyorum ama herhangi bir dilde bu sorunu olabilir gibi tartışma dışında kalmaya çalışıyordu
Tim Meyer

Evet, ancak kalıpların ve yeniden düzenlemelerin uygulanması dile bağlıdır. Örneğin @ back2dos aşağıdaki cevabında AOP önerdi, ki bu açıkça C ++ için geçerli değil.
Péter Török

SOLID ilkeleri hakkında daha fazla bilgi için lütfen programmers.stackexchange.com/questions/155852/…
LCJ

Yanıtlar:


8

Burada "Tek Sorumluluk İlkesi" ni uygulamak için farklı bir bakış açısıyla başlayabilirsiniz. Bize gösterdiğiniz (az ya da çok) sadece uygulamanızın veri modelidir. SRP burada: veri modelinizin yalnızca verileri tutmaktan sorumlu olduğundan emin olun - daha az değil, daha fazla değil.

Eğer XML dosyası okuyacakları zaman, ondan bir veri modeli oluşturmak ve SQL yazmak, ne olmalıdır değil yapmak senin içine bir şey uygulamak olduğunu TableXML veya SQL özgüdür sınıfın. Veri akışınız istediğiniz gibi:

[XML] -> ("Read XML") -> [Data model of DB definition] -> ("Write SQL") -> [SQL]

Bu nedenle, XML'ye özel kodun yerleştirilmesi gereken tek yer, örneğin, adlı bir sınıftır Read_XML. SQL'e özel kod için tek yer bir sınıf olmalıdır Write_SQL. Tabii ki, belki bu 2 görevi daha fazla alt göreve ayıracaksınız (ve sınıflarınızı birden çok yönetici sınıfına ayıracaksınız), ancak "veri modeliniz" bu katmandan herhangi bir sorumluluk almamalıdır. createStatementVeri modeli sınıflarınızın hiçbirine a eklemeyin , çünkü bu veri modelinize SQL için sorumluluk verir.

Bir Tablo'nun tüm parçalarını (ad, sütunlar, yorumlar, kısıtlamalar ...) tutmaktan sorumlu olduğunu açıklarken herhangi bir sorun görmüyorum, bu bir veri modelinin arkasındaki fikir. Ancak "Tablo" nun bazı bölümlerinin bellek yönetiminden de sorumlu olduğunu tarif ettiniz. Java veya C # gibi dillerde bu kadar kolay karşılaşmayacağınız C ++ 'a özgü bir konudur. Bu sorumluluktan kurtulmanın C ++ yolu, farklı bir katmana (örneğin, destek kitaplığı veya kendi "akıllı" işaretçi katmanınıza) sahiplik sağlamak için akıllı işaretçiler kullanmaktır. Ancak dikkat edin, döngüsel bağımlılıklarınız bazı akıllı işaretçi uygulamalarını "tahriş edebilir".

SOLID hakkında daha fazlası: işte güzel makale

http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game

SOLID'i küçük bir örnekle açıklamak. Bunu davanıza uygulamaya çalışalım:

  • Eğer sınıfları sadece gerekecek Read_XMLve Write_SQL, aynı zamanda bu 2 sınıfların etkileşimini yöneten bir üçüncü mevkii. Buna a diyelim ConversionManager.

  • DI ilkesini uygulamak buraya anlamına gelebilir: ConversionManager örneklerini oluşturmamalıdır Read_XMLve Write_SQLtek başına. Bunun yerine, bu nesneler yapıcı vasıtasıyla enjekte edilebilir. Ve inşaatçının böyle bir imzası olmalı

    ConversionManager(IDataModelReader reader, IDataModelWriter writer)

nereden miras IDataModelReaderalınan Read_XMLve IDataModelWriteraynı şey için bir arayüz Write_SQL. Bu, ConversionManageruzantıları değiştirmek zorunda kalmadan uzantıları (farklı okuyucular veya yazarlar kolayca sağlarsınız) açar - bu nedenle Açık / Kapalı prensibi için bir örneğimiz vardır. Başka bir veritabanı satıcısını desteklemek istediğinizde neyi değiştirmek istediğinizi düşünün - gerçekten, veri modelinizde hiçbir şeyi değiştirmek zorunda değilsiniz, bunun yerine başka bir SQL Yazıcı sağlayın.


Bu, SOLID'in çok makul bir uygulaması olsa da, (eski) Kayıp Holub OOP'yi oldukça anemik bir Veri Modeli için alıcılar ve ayarlayıcılar gerektirerek ihlal ettiğini unutmayın. Ayrıca meşhur Steve Yegge rant'ı hatırlatıyor .
user949300

2

Bu durumda SOLID S'yi uygulamalısınız.

Bir tablo, üzerinde tanımlanan tüm kısıtlamaları içerir. Bir kısıtlama referans aldığı tüm tabloları tutar. Sade ve basit bir model.

Buna yapıştığınız şey, ters aramalar gerçekleştirebilme, yani bazı tabloların hangi kısıtlamalara atıfta bulunduğunu bulma yeteneğidir.
Yani aslında istediğiniz bir indeksleme servisidir. Bu tamamen farklı bir görevdir ve bu nedenle farklı bir nesne tarafından gerçekleştirilmelidir.

Çok basitleştirilmiş bir sürüme bölmek için:

class Table {
      void addConstraint(Constraint constraint) { ... }
      bool removeConstraint(Constraint constraint) { ... }
      Iterator<Constraint> getConstraints() { ... }
}
class Constraint {
      //actually I am not so sure these two should be exposed directly at all
      void addReference(Table to) { ... }
      bool removeReference(Table to) { ... }
      Iterator<Table> getReferencedTables() { ... }
}
class Database {
      void addTable(Table table) { ... }
      bool removeTable(Table table) { ... }
      Iterator<Table> getTables() { ... }
}
class Index {
      Iterator<Constraint> getConstraintsReferencing(Table target) { ... }
}

Dizinin uygulanmasına gelince, 3 yol var:

  • getContraintsReferencingyöntem gerçekten sadece bütün sürünmek Databaseiçin Tableörnekleri ve bunların tarama Constraintsonucu elde etmek s. Bunun ne kadar maliyetli olduğuna ve ne sıklıkta ihtiyacınız olduğuna bağlı olarak, bir seçenek olabilir.
  • önbellek de kullanabilir. Veritabanı modeliniz tanımlandıktan sonra değişebiliyorsa, değiştiklerinde ilgili Tableve Constraintörneklerden sinyaller göndererek önbelleği koruyabilirsiniz . Biraz daha basit bir çözüm , daha sonra atılacağınız Indexbütünün bir "anlık görüntü indeksini" oluşturmaktır Database. Elbette bu ancak uygulamanız "modelleme zamanı" ile "sorgulama zamanı" arasında büyük bir ayrım yaparsa mümkündür. Bu ikisini aynı anda yapma olasılığı varsa, bu geçerli değildir.
  • Başka bir seçenek de , tüm oluşturma çağrılarını durdurmak ve dizini buna göre korumak için AOP kullanmak olacaktır .

Çok detaylı cevap, şimdiye kadar çözümünüzü beğendim! Tablo sınıfı için DI gerçekleştirdiysem, inşaat sırasındaki kısıtlamaların bir listesini verirsem ne olurdu? Zaten bir fabrika gibi davranabilir veya bu durumda bir fabrika ile birlikte çalışabilir bir TableParser sınıfı var.
Tim Meyer

Tim Meyer: DI mutlaka yapıcı enjeksiyonu değildir. DI üye işlevleri tarafından da yapılabilir. Tablo, tüm parçalarını yapıcı aracılığıyla almalıysa, bu parçaların yalnızca inşaat zamanında eklenmesini ve daha sonra asla değişmemesini veya adım adım bir tablo oluşturmak isteyip istemediğinize bağlıdır. Bu, tasarım kararınızın temelini oluşturmalıdır.
Doc Brown

1

Dairesel bağımlılıkların tedavisi, onları asla yaratmayacağınıza yemin etmektir. Önce kodlama testinin güçlü bir caydırıcı olduğunu düşünüyorum.

Her neyse, dairesel bir bağımlılıklar her zaman soyut bir temel sınıf getirilerek kırılabilir. Bu grafik gösterimleri için tipiktir. Burada tablolar düğümler ve yabancı anahtar kısıtlamaları kenarlardır. Yani soyut bir Table sınıfı ve soyut bir Constraint sınıfı ve belki de soyut bir Column sınıfı oluşturun. O zaman tüm uygulamalar soyut sınıflara bağlı olabilir. Bu mümkün olan en iyi temsil olmayabilir, ancak karşılıklı olarak birleştirilmiş sınıflara göre bir gelişmedir.

Ancak, şüphelendiğiniz gibi, bu soruna en iyi çözüm nesne ilişkilerinin izlenmesini gerektirmeyebilir. Yalnızca XML'i SQL'e çevirmek istiyorsanız, kısıtlama grafiğinin bellek içi bir temsiline ihtiyacınız yoktur. Grafik algoritmalarını çalıştırmak istiyorsanız kısıtlama grafiği iyi olurdu, ancak bundan bahsetmediniz, bu yüzden bunun bir gereklilik olmadığını varsayacağım. Desteklemek istediğiniz her SQL lehçesi için sadece bir tablolar listesi ve bir kısıtlamalar listesi ve bir ziyaretçi gerekir. Tabloları oluşturun, sonra tabloların dışındaki kısıtlamaları oluşturun. Gereksinimler değişene kadar SQL jeneratörünü XML DOM'ya bağlarken herhangi bir sorun yaşamadım. Yarın yarın için sakla.


"(Aslında bundan çok daha fazlası ama basit tutalım)" devreye giriyor. Örneğin, bir tabloyu silmem gereken durumlar olduğu için, bu tabloya herhangi bir kısıtlama atıfta bulunup bulunmadığını kontrol etmem gerekiyor.
Tim Meyer
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.