Arayüz Ayrıştırma Prensibi: Arayüzler arasında önemli bir çakışma varsa ne yapmalı?


9

Gönderen Pearson Yeni Uluslararası Sürüm: Çevik Yazılım Geliştirme, İlkeleri, Desenleri, ve Uygulamaları :

Bazen, farklı müşteri grupları tarafından çağrılan yöntemler çakışır. Örtüşme küçükse, gruplar için arayüzler ayrı kalmalıdır. Ortak işlevler, çakışan tüm arabirimlerde bildirilmelidir. Sunucu sınıfı, ortak arabirimleri bu arabirimlerin her birinden devralır, ancak yalnızca bir kez uygular.

Bob Amca, küçük çakışma olduğunda durumdan bahseder.

Önemli bir çakışma varsa ne yapmalıyız?

Söyle var

Class UiInterface1;
Class UiInterface2;
Class UiInterface3;

Class UiIterface : public UiInterface1, public UiInterface2, public UiInterface3{};

Biz arasında önemli bir örtüşme varsa ne yapmalıyım UiInterface1ve UiInterface2?


Çok örtüşen bir arayüze rastladığımda, ortak yöntemleri gruplayan ve daha sonra uzmanlıklar oluşturmak için bu ortak olandan devralan bir üst arayüz oluşturuyorum. FAKAT! Hiç kimsenin ortak arabirimi uzmanlık olmadan kullanmasını istemiyorsanız, kod çoğaltmaya gitmeniz gerekir, çünkü ortak çözüm arabirimini tanıtırsanız, insanlar bunu kullanabilir.
Andy

Soru benim için biraz belirsiz, vakalara bağlı olarak birçok farklı çözümle cevap verebiliriz. Örtüşme neden büyüdü?
Arthur Havlicek

Yanıtlar:


1

Döküm

Bu neredeyse alıntılanan kitabın yaklaşımına tam bir teğet olacaktır, ancak ISS'ye daha iyi uyum sağlamanın bir yolu, QueryInterfaceCOM-tarzı bir yaklaşım kullanarak kod tabanınızın bir merkezi alanında bir döküm zihniyetini benimsemektir.

Saf bir arayüz bağlamında örtüşen arayüzler tasarlama cazibesinin çoğu, arayüzleri kesin, keskin nişancı benzeri bir sorumluluk yerine getirmekten daha fazla "kendi kendine yeterli" yapma arzusundan gelir.

Örneğin, aşağıdaki gibi istemci işlevleri tasarlamak garip gelebilir:

// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `position` and `parenting` parameters should point to the 
// same object.
Vec2i abs_position(IPosition* position, IParenting* parenting)
{
     const Vec2i xy = position->xy();
     auto parent = parenting->parent();
     if (parent)
     {
         // If the entity has a parent, return the sum of the
         // parent position and the entity's local position.
         return xy + abs_position(dynamic_cast<IPosition*>(parent),
                                  dynamic_cast<IParenting*>(parent));
     }
     return xy;
}

... ve bu arayüzleri kullanarak ve / veya aynı nesneyi bir argüman olarak aynı nesneyi birden çok parametreye birden çok kez geçirerek hataya açık döküm yapma sorumluluğunu kaçırdığımız göz önüne alındığında, oldukça çirkin / tehlikeli. işlevi. Biz sonuna kadar Yani genellikle daha sulandırılmış endişelerini birleştirir arayüz tasarlamayı isteyen IParentingve IPositionbenzeri tek bir yerde, IGuiElemento zaman aynı şekilde daha üye işlevlere sahiptir için cazip olacak ortogonal arayüzleri kaygıları ile örtüşen duyarlı hale geldiği veya böyle bir şey aynı "kendi kendine yeterlilik" sebebi.

Döküm Sorumlulukları ve Döküm

Tamamen damıtılmış, ultra-tekil bir sorumluluğa sahip arayüzler tasarlarken, cazibe genellikle birden fazla sorumluluğu yerine getirmek için bazı downcasting'i kabul etmek veya arayüzleri birleştirmek olacaktır (ve bu nedenle hem ISP hem de SRP üzerinde yürümek).

COM tarzı bir yaklaşım (sadece bir QueryInterfaceparça) kullanarak, indirgeme yaklaşımıyla oynarız, ancak dökümünü kod tabanında tek bir merkezi yere birleştiririz ve bunun gibi bir şey yapabiliriz:

// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should implement `IPosition` and optionally `IParenting`.
Vec2i abs_position(Object* obj)
{
     // `Object::query_interface` returns nullptr if the interface is
     // not provided by the entity. `Object` is an abstract base class
     // inherited by all entities using this interface query system.
     IPosition* position = obj->query_interface<IPosition>();
     assert(position && "obj does not implement IPosition!");
     const Vec2i xy = position->xy();

     IParenting* parenting = obj->query_interface<IParenting>();
     if (parenting && parenting->parent()->query_interface<IPosition>())
     {
         // If the entity implements IParenting and has a parent, 
         // return the sum of the parent position and the entity's 
         // local position.
         return xy + abs_position(parenting->parent());
     }
     return xy;
}

... tabii ki tip güvenli ambalajlar ve ham işaretçilerden daha güvenli bir şey elde etmek için merkezi olarak inşa edebileceğiniz her şeyle umarım.

Bununla, örtüşen arayüzler tasarlama cazibesi genellikle mutlak minimum seviyeye indirgenir. ISS hakkında endişelenmeden istediğinizi karıştırabileceğiniz ve eşleştirebileceğiniz ve C ++ 'da çalışma zamanında yalancı ördek yazmanın esnekliğini elde edebileceğiniz çok tekil sorumluluklara (bazen sadece bir üye işlevi) sahip arayüzler tasarlamanıza izin verir. belirli bir arabirimi destekleyip desteklemediklerini görmek için nesneleri sorgulamak için çalışma zamanı cezalarının takas edilmesi). Çalışma zamanı kısmı, işlevlerin bu arayüzleri uygulayan eklentilerin derleme zamanı bilgilerine sahip olmayacağı bir yazılım geliştirme kitine sahip bir ayarda önemli olabilir.

Şablonlar

Şablonlar bir olasılıksa (bir nesneyi ele geçirdiğimiz zaman kaybolmayan gerekli derleme zamanı bilgisine önceden sahibiz), o zaman bunu yapabiliriz:

// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should have `position` and `parent` methods.
template <class Entity>
Vec2i abs_position(Entity& obj)
{
     const Vec2i xy = obj.xy();
     if (obj.parent())
     {
         // If the entity has a parent, return the sum of the parent 
         // position and the entity's local position.
         return xy + abs_position(obj.parent());
     }
     return xy;
}

... elbette böyle bir durumda, parentyöntem aynı Entitytürü döndürmek zorunda kalacaktı , bu durumda muhtemelen arayüzlerden açıkça kaçınmak istiyoruz (çünkü genellikle temel işaretçilerle çalışmak lehine tip bilgilerini kaybetmek isteyeceklerdir).

Varlık Bileşen Sistemi

COM tarzı yaklaşımı esneklik veya performans açısından daha ileriye götürmeye başlarsanız, genellikle oyun motorlarının endüstride uyguladığı şeye benzer bir varlık bileşeni sistemi elde edersiniz. Bu noktada, birçok nesne yönelimli yaklaşıma tamamen dik olacaksınız, ancak ECS, GUI tasarımına uygulanabilir (sahne odaklı bir odak dışında ECS'yi kullanmayı düşündüğüm bir yer, ancak daha sonra çok geç kabul edildi) orada denemek için COM tarzı bir yaklaşımı benimsemek).

Bu COM tarzı çözümün GUI araç seti tasarımları gittikçe tamamen dışarıda olduğunu ve ECS'nin daha da fazla olacağını unutmayın, bu yüzden birçok kaynak tarafından desteklenecek bir şey değildir. Yine de, üst üste gelen sorumlulukları mutlak minimumda olan, genellikle endişe duymayan arayüzler tasarlama cazibelerini azaltmanıza kesinlikle izin verecektir.

Pragmatik Yaklaşım

Alternatif, elbette, sizin bekçi biraz dinlenmek veya ayrıntılı düzeyde arayüzler tasarlayabilir ve sonra, kullandıkları kaba arayüzleri oluşturmak için bunları miras başlamak gibi IPositionPlusParentinghangi ikisi türetilmiştir IPositionveIParenting(umarım bundan daha iyi bir isimle). Saf arabirimlerle, ISS'nin yaygın olarak uygulanan monolitik derin hiyerarşik yaklaşımlar kadar (Qt, MFC, vb.), Belgelerin genellikle bu türlerle aşırı ISP'yi ihlal ettiği göz önüne alındığında ilgisiz üyeleri gizleme ihtiyacı hissettiği kadar ihlal etmemelidir. pragmatik bir yaklaşım burada ve orada bir miktar örtüşmeyi kabul edebilir. Ancak bu tür COM tarzı yaklaşım, kullanacağınız her kombinasyon için birleştirilmiş arayüzler oluşturma ihtiyacını ortadan kaldırır. Bu tür durumlarda "kendi kendine yeterlilik" kaygısı tamamen ortadan kaldırılmıştır ve bu, hem SRP hem de ISS ile savaşmak isteyen örtüşen sorumluluklara sahip tasarım arayüzlerine yönelik nihai cazip kaynağını ortadan kaldıracaktır.


11

Bu, her durum için ayrı ayrı yapmanız gereken bir karar çağrısıdır.

Her şeyden önce, SOLID ilkelerinin sadece ... ilkeleri olduğunu unutmayın. Kural değiller. Gümüş bir kurşun değiller. Onlar sadece ilkeler. Bu onların öneminden uzaklaşmamak için, onları takip etmeye her zaman eğilmelisiniz. Ama ikincisi bir miktar acı veriyorlarsa, ihtiyacınız olana kadar onları atmalısınız.

Bunu akılda tutarak, neden arayüzlerinizi ilk etapta ayırdığınızı düşünün. Bir arabirim fikri "Bu tüketen kod tüketilen sınıfta uygulanacak bir dizi yöntem gerektiriyorsa, uygulama üzerinde bir sözleşme belirlemem gerekiyor: Bana bu arabirim ile bir nesne sağlarsanız, çalışabilirim Bununla birlikte."

ISS'nin amacı "Gereksinim duyduğum sözleşme sadece mevcut bir arayüzün bir alt kümesiyse, mevcut arayüzü, yöntemime aktarılabilecek gelecekteki herhangi bir sınıfta zorlamamalıyım" demektir.

Aşağıdaki kodu göz önünde bulundurun:

public interface A
{
    void X();
    void Y();
}

public class Foo
{
     public void ConsumeX(A a)
     {
         a.X();
     }
}

Şimdi yeni bir nesneyi ConsumeX'e aktarmak istiyorsak, sözleşmeye uymak için X () ve Y () uygulamak zorunda olduğu bir durumumuz var.

Şimdi kodu bir sonraki örneğe benzeyecek şekilde değiştirmeli miyiz?

public interface A
{
    void X();
    void Y();
}

public interface B
{
    void X();
}

public class Foo
{
     public void ConsumeX(B b)
     {
         b.X();
     }
}

ISS yapmamızı önerdi, bu yüzden bu karara yaslanmalıyız. Ancak, bağlam olmadan, emin olmak zor. A ve B'yi uzatmamız muhtemel mi? Bağımsız olarak genişlemeleri muhtemel mi? B'nin A'nın gerektirmediği yöntemleri uygulaması muhtemel mi? (Değilse, A'yı B'den türetebiliriz.)

Bu, yapmanız gereken yargılama çağrısıdır. Ve gerçekten bu çağrıyı yapmak için yeterli bilgiye sahip değilseniz, muhtemelen en basit seçeneği kullanmalısınız, bu da ilk kod olabilir.

Neden? Çünkü daha sonra fikrinizi değiştirmek kolaydır. Bu yeni sınıfa ihtiyacınız olduğunda, yeni bir arayüz oluşturun ve her ikisini de eski sınıfınıza uygulayın.


1
"Her şeyden önce, SOLID prensiplerinin sadece ... prensipler olduğunu unutmayın. Kural değiller. Onlar gümüş bir kurşun değil. Onlar sadece prensipler. Bu önemlerinden uzaklaşmamak için, daima yalın olmalısın Ama ikincisi bir miktar acı veriyorlarsa, onlara ihtiyaç duyana kadar onları terk etmelisin. " Bu, her tasarım deseni / ilkesi kitabının ilk sayfasında olmalıdır. Ayrıca her 50 sayfada bir hatırlatma olarak görünmelidir.
Christian Rodriguez
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.