Bir nesnenin yetenekleri, yalnızca uyguladığı arabirimler tarafından tanımlanmalı mı?


36

C # isoperatörü ve Java'nın instanceofoperatörü, bir nesne örneğinin uyguladığı arabirime (ya da daha temel olarak, temel sınıfına) dal vermenize izin verir.

Bir arayüzün sağladığı yeteneklere dayanarak bu özelliği yüksek seviye dallanma için kullanmak uygun mudur?

Veya bir temel sınıf, bir nesnenin sahip olduğu yetenekleri tanımlayan bir arayüz sağlamak için boolean değişkenler sağlamalıdır mı?

Örnek:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

vs.

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Yetenek tespiti için arayüz uygulamasını kullanmak için herhangi bir tuzak var mı?


Bu örnek sadece bir örnekti. Daha genel bir uygulama hakkında merak ediyorum.


10
İki kod örneğine bak. Hangisi daha okunaklı?
Robert Harvey,

39
Sadece benim düşüncem, ama ilkine ben gideceğim, çünkü uygun tipte güvenle kullanabileceğinizi garanti ediyor. İkincisi, CanResetPasswordbunun yalnızca bir şey uygulandığında geçerli olacağını varsayar IResetsPassword. Şimdi bir sistemin, tip sisteminin kontrol etmesi gereken bir şey belirlemesini sağlıyorsunuz (ve nihayetinde oyuncu kadrosunu hazırladığınızda olacaktır).
Becuzz

10
@nocomprende: xkcd.com/927
Robert Harvey

14
Soru başlığı ve soru gövdesi farklı şeyler soruyor. Başlığı cevaplamak için: İdeal olarak, evet. Gövdeye hitap etmek için: Kendinizi bir tür denetleme ve elle döküm işlemi görürseniz, büyük olasılıkla tür sisteminizi doğru şekilde kullanmayacaksınız. Bize bu durumda kendinizi nasıl bulduğunuzu daha spesifik olarak anlatabilir misiniz?
MetaFight

9
Bu soru basit görünebilir, ancak düşünmeye başladığınızda oldukça genişler. Aklıma gelen sorular: Mevcut hesaplara yeni davranışlar eklemeyi veya farklı davranışlara sahip hesap türleri oluşturmayı mı bekliyorsunuz? Tüm hesap türlerinde benzer davranışlar var mı ya da aralarında büyük farklılıklar var mı? Örnek kod tüm hesap türlerini veya sadece temel IAccount arayüzünü biliyor mu?
Öforik

Yanıtlar:


7

Mümkünse indirmeyi önlemek için en iyisi olduğu konusunda, ilk sebep iki nedenden ötürü tercih edilir:

  1. tip sisteminin sizin için çalışmasına izin veriyorsunuz. İlk örnekte, eğer isyangın çıkarsa , downcast kesinlikle çalışacaktır. İkinci formda, yalnızca uygulayan nesnelerin IResetsPasswordbu özellik için doğru döndürdüğünü el ile sağlamanız gerekir
  2. kırılgan taban sınıfı. Eklemek istediğiniz her arabirim için temel sınıfa / arabirime bir özellik eklemeniz gerekir. Bu hantal ve hataya açık.

Bu, bu sorunları bir şekilde düzeltemeyeceğiniz anlamına gelmez. Örneğin, temel sınıfın dahil edilmeyi kontrol edebileceğiniz bir dizi yayınlanmış arayüze sahip olmasını sağlayabilirsiniz.

BTW benim doğal tercihim, kalıtım konusundaki kompozisyon, ancak çoğunlukla duruma göre. Arayüz devralma genellikle o kadar kötü değildir. Ayrıca, kendinizi fakir bir adamın tip hiyerarşisini uygularsanız, derleyicinin size yardımcı olacağı halihazırda orada olanı kullanmaktan daha iyi olursunuz.


Ben de kompozisyon yazmayı çok tercih ederim. Bana bu özel örneğin uygun bir şekilde kaldıraç olmadığını görmek için +1. Yine de, yetenekler geniş ölçüde değiştiğinde (nasıl uygulandıklarına göre) hesaplamalı yazmanın (geleneksel miras gibi) daha sınırlı olduğunu söyleyebilirim. Bu yüzden, kompozisyonel tipleme veya standart kalıtımdan farklı bir problemi çözen arayüzleme yaklaşımı.
TheCatWhisperer,

(account as IResettable)?.ResetPassword();var resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
çekleri

89

Bir nesnenin yetenekleri, yalnızca uyguladığı arabirimler tarafından tanımlanmalı mı?

Nesnelerin yetenekleri hiç tanımlanmamalıdır.

Bir nesneyi kullanan müşterinin nasıl çalıştığı hakkında hiçbir şey bilmesi gerekmemelidir. Müşteri, yalnızca nesneye yapmasını söyleyebileceği şeyleri bilmelidir. Nesnenin yaptığı, söylendiğinde, müşterinin sorunu değil.

Yani

if (account is IResetsPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

veya

if (account.CanResetPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

düşünmek

account.ResetPassword();

veya

account.ResetPassword(authority);

çünkü accountbu işe yarayacak mı zaten biliyor. Neden sordun? Sadece ne istediğini söyle ve ne yaparsa yapsın.

Bunun, müşterinin işe yarayıp yaramadığını umursamadığı bir şekilde yapıldığını düşünün, çünkü bu bir sorun yaratır. Müşterilerin işi sadece girişimde bulunmaktı. Bunu şimdi yaptım ve başa çıkacak başka şeyler de var. Bu stildeki birçok isim var ama en çok sevdiğim isim söylesin, sorma .

İstemciyi yazarken her şeyi takip etmeniz gerektiğini ve bu nedenle bilmeniz gerektiğini düşündüğünüz her şeyi kendinize çekmeniz çok caziptir. Bunu yaptığınızda nesneleri tersine çevirirsiniz. Cehaletine değer ver. Ayrıntıları uzaklaştırın ve nesnelerin bunlarla ilgilenmesine izin verin. Ne kadar az bilirsen o kadar iyi.


54
Polimorfizm sadece tam olarak ne konuştuğumu bilmediğim veya umursamadığım zaman çalışır. Yani bu durumla başa çıkmamın yolu, dikkatlice kaçınmak.
candied_orange

7
Müşterinin, girişimin başarılı olup olmadığını gerçekten bilmesi gerekiyorsa, yöntem bir boole döndürür. if (!account.AttemptPasswordReset()) /* attempt failed */;Bu şekilde müşteri sonucu bilir, ancak sorudaki karar mantığıyla ilgili sorunlardan kaçınır. Deneme asenkronize ise, bir geri arama iletirsiniz.
Radiodef

5
@DavidZ İkimiz de İngilizce biliyoruz. Bu yüzden size bu yorumu iki kez yükseltmenizi söyleyebilirim. Bu yapabileceğin anlamına mı geliyor? Yapmanı söyleyemeden önce yapabilir misin, bilmek zorunda mıyım?
candied_orange

5
@QPaysTaxes sizin için bir hata eylemcisinin yapıldığı sırada aktarıldığını biliyorsunuz account. Bu noktada pop-up'lar, günlük kaydı, çıktılar ve sesli uyarılar oluyor olabilir. Müşterinin işi "şimdi yap" demek. Bundan daha fazlasını yapmak zorunda değildir. Her şeyi tek bir yerde yapmak zorunda değilsin.
candied_orange

6
@QPaysTaxes Başka bir yerde ve çeşitli şekillerde ele alınabilirdi, bu konuşmaya gerek kalmaz ve sadece suları çamurlardı.
Tom.Bowen89

17

Arka fon

Kalıtım, nesne yönelimli programlamada bir amaca hizmet eden güçlü bir araçtır. Ancak, her sorunu zarif bir şekilde çözemez: bazen diğer çözümler daha iyidir.

Erken bilgisayar bilimi derslerinize (CS dereceniz varsayarsak) geri dönmeyi düşünüyorsanız, müşterinin yazılımdan ne yapmak istediğini belirten bir paragraf veren bir profesörü hatırlayabilirsiniz. İşiniz paragrafı okumak, aktörleri ve eylemleri tanımlamak ve sınıfların ve yöntemlerin ne olduğu hakkında kaba bir taslak oluşturmaktır. İçinde önemli gibi görünen ama önemli olmayan bazı serseri uçları olacak. Gereklilikleri yanlış yorumlamanın çok gerçek bir olasılığı var.

Bu, en deneyimli kişilerin bile yanlış anladığı önemli bir beceridir: gereksinimleri doğru bir şekilde belirlemek ve bunları makine dillerine çevirmek.

Senin sorun

Yazdıklarınıza bağlı olarak, sınıfların gerçekleştirebileceği isteğe bağlı eylemlerin genel durumunu yanlış anladığınızı düşünüyorum. Evet, kodunuzun sadece bir örnek olduğunu ve genel durumla ilgilendiğinizi biliyorum. Ancak, bir nesnenin belirli alt türlerinin bir eylem gerçekleştirebileceği durumun nasıl ele alınacağını bilmek istediğiniz gibi görünüyor , ancak diğer alt türler yapamıyor.

Sırf accounthesap gibi bir nesnenin bir OO dilinde bir türe çevrildiği anlamına gelmez. Bir insan dilinde "yazın" her zaman "sınıf" anlamına gelmez. Bir hesap bağlamında "tip", "izin kümesi" ile daha yakından ilişkili olabilir. Bir eylemi gerçekleştirmek için bir kullanıcı hesabı kullanmak istiyorsunuz, ancak bu işlem bu hesap tarafından gerçekleştirilebilir veya gerçekleştirilemeyebilir. Miras kullanmak yerine, bir temsilci veya güvenlik belirteci kullanırdım.

Çözümüm

Gerçekleştirebileceği birkaç isteğe bağlı işlemi olan bir hesap sınıfını düşünün. Miras yoluyla "X eylemi" gerçekleştirme yerine, neden hesabın bir temsilci nesnesi (şifre sıfırlayıcı, form gönderici, vb.) Veya bir erişim belirteci döndürmesini istemiyorsunuz?

account.getPasswordResetter().doAction();
account.getFormSubmitter().doAction(view.getContents());

AccountManager.resetPassword(account, account.getAccessToken());

Son seçenekte fayda var ne kullanmak istiyorsanız benim birisi sıfırlamak için hesap kimlik bilgileri başka şifresini?

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Sistem sadece daha esnek değil, yalnızca tip atmalarını kaldırmamı değil, tasarım daha da etkileyici . Bunu okuyabilir ve ne yaptığını ve nasıl kullanılabileceğini kolayca anlayabilirim.


TL; DR: Bu bir XY problemi gibi görünüyor . Genel olarak alt seçenek olan iki seçenekle karşı karşıya kaldığımızda, bir adım geri atmaya ve “ burada gerçekten ne yapmaya çalışıyorum? yazım tamamıyla kaldırılsın mı? "


1
Tasarım için + 1 ifadesini kullanmasanız bile kokuyor.
Jared Smith,

sıfırlama şifresinin türe bağlı olarak tamamen farklı argümanların olduğu durumlar ne olacak? ResetPassword (pswd) vs ResetPasswordToRandom ()
TheCatWhisperer

1
@TheCatWhisperer İlk örnek, farklı bir işlem olan "parola değiştir" dir. İkinci örnek, bu cevapta açıkladığım şeydir.

Neden olmasın account.getPasswordResetter().resetPassword();?
15'de

1
The benefit to the last option there is what if I want to use my account credentials to reset someone else's password?.. kolay. Diğer hesap için bir Hesap etki alanı nesnesi oluşturup bu hesabı kullanın. Statik sınıflar, ünite testi için sorunludur (bu yöntem, tanım gereği bazı depolama alanlarına erişim gerektirir, bu nedenle artık o sınıfa daha fazla dokunan herhangi bir kodu kolayca test edemezsiniz) ve bundan kaçınılması en iyisidir. Başka bir arabirim döndürme seçeneği genellikle iyi bir fikirdir, ancak altta yatan sorunla gerçekten ilgilenmez (sorunu sadece diğer arabirime taşıdınız).
Voo

1

Bill Venner , yaklaşımınızın kesinlikle iyi olduğunu söylüyor ; 'instanceof ne zaman kullanılır' başlıklı bölüme atlayın. Yalnızca belirli bir davranışı uygulayan belirli bir türe / arabirime indirgeyeceksin, bu yüzden bu konuda seçici davranma konusunda kesinlikle haklısın.

Bununla birlikte, başka bir yaklaşım kullanmak istiyorsanız, yakınızı tıraş etmenin birçok yolu vardır.

Polimorfizm yaklaşımı var; Tüm hesapların bir şifresi olduğunu iddia edebilirsiniz, bu nedenle tüm hesapların en azından bir şifreyi sıfırlamayı deneyebilmesi gerekir. Bu, temel hesap sınıfında, resetPassword()tüm hesapların uygulayacakları ancak kendi yöntemleriyle uygulamamız gereken bir yöntem olması gerektiği anlamına gelir . Sorun, yetenek orada olmadığı zaman bu yöntemin nasıl davranması gerektiğidir.

Bir boşa döndürebilir ve şifreyi sıfırlayıp sıfırlamamasına sessizce tamamlayabilir, belki şifreyi sıfırlamazsa mesajı dahili olarak yazdırmak için sorumluluk alabilir. En iyi fikir değil.

Sıfırlamanın başarılı olup olmadığını belirten bir boole döndürülebilir. Bu boole'yi açarak, şifre sıfırlama işleminin başarısız olduğunu söyleyebiliriz.

Parola sıfırlama girişiminin sonucunu gösteren bir Dize döndürür. Dize, sıfırlamanın neden başarısız olduğu ve çıktı alınabileceği hakkında daha fazla ayrıntı verebilir.

Daha fazla ayrıntı taşıyan ve önceki tüm dönüş öğelerini birleştiren bir ResetResult nesnesini döndürür.

Bir boşluğu döndürebilir ve bu özelliğe sahip olmayan bir hesabı sıfırlamaya çalışırsanız bir istisna atabilir (bunu, normal akış kontrolü için istisna işlemeyi kullanmanın çeşitli nedenlerle kötü bir uygulama olduğu gibi yapmayın).

Bir eşleştirme canResetPassword()yöntemine sahip olmak , dünyadaki en kötü şey gibi görünmeyebilir, çünkü bu, yazıldığında sınıfa yerleşik statik bir yetenektir. Bu, yöntem yaklaşımının neden kötü bir fikir olduğuna dair bir ipucudur, ancak kabiliyetin dinamik olduğunu ve canResetPassword()değişebileceğini öne sürdüğü için, izin istemek ve çağrı yapmak arasında değişebileceği ihtimalini de beraberinde getirir. Başka yerlerde de belirtildiği gibi, izin istemek yerine söyle.

Kalıtım üzerindeki kompozisyon bir seçenek olabilir: passwordResetteraramadan önce boş için kontrol edebileceğiniz bir son alana (veya eşdeğer bir alıcıya) ve eşdeğer sınıflara sahip olabilirsiniz. İzin istemek gibi biraz hareket etse de, kesinlik, çıkarılan herhangi bir dinamik yapının önüne geçebilir.

İşlevselliği kendi sınıfına bir parametre olarak hesaba katabilecek ve onun üzerinde hareket edebilecek (örneğin resetter.reset(account)) işlevselliğini dışa aktarabilir , ancak bu genellikle de kötü bir uygulamadır (ortak bir benzetme para almak için cüzdanınıza ulaşan bir dükkâncıdır).

Özelliğe sahip dillerde, karışımları veya özellikleri kullanabilirsiniz, ancak bu özelliklerin varlığını kontrol edebileceğiniz bir konumda olursunuz.


Tüm hesapların bir şifresi olmayabilir (yine de bu belirli sistem açısından). Mesela benim hesabım 3. parti ... google, facebook, vb. Bunun iyi bir cevap olmadığını söyleme!
TheCatWhisperer,

0

Bu özel Örneğin " bir şifre sıfırlama ", ben miras üzerinde kompozisyon kullanarak öneriyoruz (bu durumda, bir arayüz / sözleşme miras). Çünkü bunu yaparak:

class Foo : IResetsPassword {
    //...
}

Derhal (derleme zamanında) sınıfınızın ' bir şifreyi sıfırlayabileceğini ' belirtiyorsunuz . Ancak, senaryonuzda, kabiliyetin varlığı şartlı ve diğer şeylere bağlıysa, derleme zamanında bir şey belirtemezsiniz. O zaman şunu yapmayı öneriyorum:

class Foo {

    PasswordResetter passwordResetter;

}

Şimdi, çalışma zamanında, myFoo.passwordResetter != nullbu işlemi yapmadan önce olup olmadığını kontrol edebilirsiniz . Eşyaları daha da fazla ayırmak istiyorsanız (ve daha fazla özellik eklemeyi planlıyorsanız) şunları yapabilirsiniz:

class Foo {
    //... foo stuff
}

class PasswordResetOperation {
    bool Execute(Foo foo) { ... }
}

class SendMailOperation {
    bool Execute(Foo foo) { ... }
}

//...and you follow this pattern for each new capability...

GÜNCELLEME

OP'den bazı cevaplar ve yorumlar okuduktan sonra, sorunun bileşimsel çözümle ilgili olmadığını anladım. Bu nedenle, sorunun genel olarak aşağıdaki gibi bir senaryoda nesnelerin yeteneklerinin nasıl daha iyi tanımlanabileceğiyle ilgili olduğunu düşünüyorum:

class BaseAccount {
    //...
}
class GuestAccount : BaseAccount {
    //...
}
class UserAccount : BaseAccount, IMyPasswordReset, IEditPosts {
    //...
}
class AdminAccount : BaseAccount, IPasswordReset, IEditPosts, ISendMail {
    //...
}

//Capabilities

interface IMyPasswordReset {
    bool ResetPassword();
}

interface IPasswordReset {
    bool ResetPassword(UserAccount userAcc);
}

interface IEditPosts {
    bool EditPost(long postId, ...);
}

interface ISendMail {
    bool SendMail(string from, string to, ...);
}

Şimdi, belirtilen tüm seçenekleri analiz etmeye çalışacağım:

OP ikinci örnek:

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Diyelim ki bu kod bazı temel hesap sınıflarını alıyor (örneğin: BaseAccountbenim örneğimde); Bu kötü bir durumdur çünkü temel sınıfa boole'lar ekler, orada olması hiç mantıklı olmayan bir kodla kirletir.

OP ilk örnek:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Soruyu cevaplamak için, bu önceki seçenekten daha uygundur, ancak uygulamaya bağlı olarak, katı L prensibini kıracak ve muhtemelen bu gibi kontroller kod boyunca yayılacak ve daha fazla bakım yapılmasını zorlaştıracaktır.

CandiedOrange'in yanıtı:

account.ResetPassword(authority);

Bu ResetPasswordyöntem BaseAccountsınıfa eklenirse, OP'nin ikinci örneğinde olduğu gibi temel sınıfı da uygunsuz kodla kirletiyor

Snowman'ın cevabı:

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Bu iyi bir çözüm, ancak yeteneklerin dinamik olduğunu ve zaman içinde değişebileceğini düşünüyor. Bununla birlikte, OP'den birkaç yorum okuduktan sonra, burada konuşmanın polimorfizm ve statik olarak tanımlanmış sınıflarla ilgili olduğunu düşünüyorum (her ne kadar sezgisel hesapların dinamik senaryoya işaret etmesine rağmen). EG: Bu AccountManagerörnekte izin kontrolü DB'ye sorgular olacaktır; OP sorusundaki kontroller, nesneleri atma girişimleridir.

Benden başka bir öneri:

Yüksek düzeyde dallanma için Şablon Yöntemi desenini kullanın. Bahsedilen sınıf hiyerarşisi olduğu gibi tutulur; temel sınıfı kirleten atmalardan ve uygun olmayan özelliklerden / yöntemlerden kaçınmak için yalnızca nesneler için daha uygun işleyiciler yaratırız.

//Template method
class BaseAccountOperation {

    BaseAccount account;

    void Execute() {

        //... some processing

        TryResetPassword();

        //... some processing

        TrySendMail();

        //... some processing
    }

    void TryResetPassword() {
        Print("Not allowed to reset password with this account type!");
    }

    void TrySendMail() {
        Print("Not allowed to reset password with this account type!");
    }
}

class UserAccountOperation : BaseAccountOperation {

    UserAccount userAccount;

    void TryResetPassword() {
        account.ResetPassword(...);
    }

}

class AdminAccountOperation : BaseAccountOperation {

    AdminAccount adminAccount;

    override void TryResetPassword() {
        account.ResetPassword(...);
    }

    void TrySendMail() {
        account.SendMail(...);
    }
}

Bir nesneyi / bir karma tablo kullanarak işlemi uygun hesap sınıfına bağlayabilir veya uzantı yöntemlerini kullanarak çalışma zamanı işlemleri gerçekleştirebilir, dynamicanahtar sözcük kullanabilir veya son seçenek olarak , hesap nesnesini işleme iletmek için yalnızca bir döküm kullanabilirsiniz . bu durumda, atma sayısı işlemin başında sadece bir tanedir).


1
Her zaman "Execute ()" yöntemini uygulamak işe yarar mıydı, ama bazen hiçbir şey yapmıyor mu? Bir bool döndürür, sanırım yapıp yapmadığı anlamına gelir. Bu müşteriye geri atar: "Şifreyi sıfırlamaya çalıştım, ama olmadı (ne sebeple olursa olsun), bu yüzden şimdi yapmalıyım ..."

4
"CanX ()" ve "doX ()" yöntemlerine sahip olmak bir kesişme önleyicidir: bir arabirim bir "doX ()" yöntemini ifşa ederse, X'in işlem görmemesi durumunda bile (örneğin, boş nesne kalıbı) X yapabilir ). Gerektiğini asla zamansal bağlantı girişme bir arayüz kullanıcıları üzerinde görevdeki olmak bu çok kolay yanlış ve kırılma şeyler elde etmektir çünkü (yöntem A yöntemi B öncesi çağırmak).

1
Arayüzlere sahip olmak, kalıtımın üstünde kompozisyon kullanımı üzerinde hiçbir etkiye sahiptir. Sadece mevcut olan bir işlevselliği temsil ederler. Bu işlevselliğin nasıl ortaya çıktığı, arayüzün kullanımından bağımsızdır. Burada yaptığınız, herhangi bir yararı olmayan bir arayüzün geçici bir uygulamasını kullanmaktır.
Miyamoto Akira

İlginç: en çok oy alan cevap temelde yukarıdaki yorumumun söylediklerini söylüyor. Bazı günler sadece şanslısın.

@nocomprende - evet, cevabımı çeşitli yorumlara göre güncelledim. Geri bildiriminiz için teşekkür ederiz :)
Emerson Cardoso

0

Sunulan seçeneklerin hiçbiri iyi OO değil. Bir nesnenin türü etrafında ifadeler varsa yazıyorsanız, büyük olasılıkla yanlış OO yapıyorsunuzdur (istisnalar var, bu bir değil.) İşte sorunuzun basit OO cevabı (geçerli C # olmayabilir):

interface IAccount {
  bool CanResetPassword();

  void ResetPassword();

  // Other Account operations as needed
}

public class Resetable : IAccount {
  public bool CanResetPassword() {
    return true;
  }

  public void ResetPassword() {
    /* RESET PASSWORD */
  }
}

public class NotResetable : IAccount {
  public bool CanResetPassword() {
    return false;
  }

  public void ResetPassword() {
    Print("Not allowed to reset password with this account type!");}
  }

Bu örneği, orijinal kodun yaptığı ile eşleşecek şekilde değiştirdim. Bazı yorumlara dayanarak, insanlar buradaki 'doğru' özel kod olup olmadığına takılıyor gibi görünüyor. Bu, bu örneğin amacı değil. Bütün Polimorfik aşırı yükleme, esasen, nesnenin türüne bağlı olarak farklı mantık uygulamalarını koşullu olarak yürütmek içindir. Her iki örnekte de yaptığınız şey, dilinizin size bir özellik olarak verdiği şeyi elle sıkmaktır. Özet olarak, alt türlerden kurtulabilir ve Hesap türünün bir boolean özelliği olarak sıfırlama özelliğini kullanabilirsiniz (alt türlerin diğer özelliklerini göz ardı ederek).

Dizayn hakkında daha geniş bir görüş olmadan, bunun sizin sisteminiz için iyi bir çözüm olup olmadığını söylemek mümkün değildir. Basittir ve yaptığınız şey için işe yararsa, birisi ResetPassword () işlevini çağırmadan önce CanResetPassword () 'i kontrol etmeyi başaramazsa, bu konuda bir daha düşünmeniz gerekmeyecektir. Ayrıca bir boole döndürür veya sessizce başarısız olabilir (önerilmez). Bu gerçekten tasarımın özelliklerine bağlıdır.


Tüm hesaplar sıfırlanamaz. Ayrıca bir hesap sıfırlanabilir olmaktan daha fazla işlevselliğe sahip olabilir mi?
TheCatWhisperer,

2
"Tüm hesaplar sıfırlanamaz." Bu düzenlemeden önce yazıldığından emin değilim. İkinci sınıftır NotResetable. “bir hesap sıfırlanabilir olmaktan daha fazla işlevselliğe sahip olabilir” diye beklerdim ancak eldeki soru için önemli görünmüyor.
JimmyJames

1
Bu çözüm doğru yönde ilerliyor, bence OO içinde güzel bir çözüm sunuyor. Arabirim arabirimlerini ayırmak için, arabirim adını IPasswordReseter (veya bu etkiye yönelik bir şey) olarak değiştirir. IAccount, diğer çoklu arayüzleri destekleyici olarak ilan edilebilir. Bu, daha sonra etki alanı nesnelerinde delegasyon tarafından oluşturulan ve kullanılan uygulama ile ilgili sınıflara sahip olma seçeneğini sunar. @JimmyJames, küçük nitpick, C # üzerinde her iki sınıfın da IAccount kullandıklarını belirtmeleri gerekir. Aksi halde kod biraz garip görünüyor ;-)
Miyamoto Akira

1
Ayrıca, CanX () ve DoX () hakkındaki diğer cevaplardan birinde Snowman'dan gelen bir yorumu kontrol edin. Her zaman bir anti-patern değil, kullandığı gibi (örneğin, kullanıcı arayüzü davranışı üzerine)
Miyamoto Akira

1
Bu cevap iyi başlıyor: bu kötü bir OOP. Maalesef, çözümünüz aynı derecede kötü çünkü aynı zamanda tip sistemini de altüst ediyor, ancak bir istisna atıyor. İyi bir çözüm farklı görünüyor: bir tür üzerinde uygulanmamış yöntemler yok. .NET framework aslında bazı türler için yaklaşımınızı kullanır ancak bu açıkça bir hataydı.
Konrad Rudolph

0

Cevap

Sorunuza biraz düşündüğüm cevabı:

Bir nesnenin yetenekleri, bir arayüz uygulanarak değil, kendileri aracılığıyla tanımlanmalıdır. Statik olarak, kesin olarak yazılmış bir dil bu olasılığı sınırlayacaktır (tasarım gereği).

Zeyilname:

Nesne türünde dallanma kötüdür.

Başıboş

c#Etiket eklemediğiniz , ancak genel bir object-orientedbağlamda sorduğunuza inanıyorum .

Açıkladığınız şey, genel olarak "OO" özelliğine sahip olmayan, statik / güçlü yazılan dillerin teknikliğidir. Sorununuz, diğerleri arasında, derleme zamanında yeterince dar bir tür olmadan (bu değişken değişken, yöntem parametresi / dönüş değeri tanımı veya açık bir döküm yoluyla) bu dillerdeki yöntemleri arayamayacağınız gerçeğinden kaynaklanmaktadır. Her iki türünüzü de geçmişte, bu tür dillerde yaptım ve ikisi de bugünlerde bana çirkin görünüyor.

Dinamik olarak yazılmış OO dillerinde, bu sorun "ördek yazma" olarak adlandırılan bir kavram nedeniyle ortaya çıkmaz. Kısacası, bu temelde hiçbir yerde bir nesnenin türünü beyan etmeyeceğiniz anlamına gelir (oluştururken hariç). Mesajları nesnelere gönderirsiniz ve nesneler bu mesajları kullanır. Nesnenin gerçekte bu isimde bir metodu olup olmadığı umrunda değil. Bazı diller method_missing, bunu daha da esnek yapan genel, hepsini yakalayan bir yönteme sahiptir.

Böylece, böyle bir dilde (örneğin, Ruby), kodunuz olur:

account.reset_password

Dönemi.

accountYa şifreyi sıfırlamak bir "reddedildi" istisna veya bir " 'reset_password' nasıl işleneceğini bilmiyorum" özel durum oluşturur.

Her ne sebeple olursa olsun açıkça dallanmanız gerekiyorsa, yine de sınıfta değil de, yeteneklerinizde bunu yaparsınız:

account.reset_password  if account.can? :reset_password

( can?Nesnenin bir yöntemi çağırmadan yürütüp yürütemeyeceğini denetleyen bir yöntemdir.)

Kişisel tercihim şu anda dinamik olarak yazılmış dillerde olsa da, bunun yalnızca kişisel bir tercih olduğunu unutmayın. Statik olarak yazılmış dillerin kendileri için de geçerli olan şeyleri vardır, bu yüzden lütfen bu karışıklığı, statik olarak yazılmış dillere karşı bash olarak almayın ...


Bakış açınızı burada görmek harika! Java / C # tarafında farklı bir OOP dalı olduğunu unutmaya meyilliyiz. Meslektaşlarıma söylediğimde, JS (tüm sorunları için), birçok yönden, C # 'dan daha fazla OO, bana gülüyorlar!
TheCatWhisperer,

C # 'yı seviyorum ve bence genel amaçlı bir dil, ama kişisel görüşüm, OO açısından oldukça zayıf. Hem özellikler hem de uygulamada, prosedürel programlamayı teşvik etme eğilimindedir.
TheCatWhisperer,

2
Muhtemelen, ördek tipinde bir dilde bir yöntemin varlığının test edilmesi, statik olarak yazılmış bir dilde bir arayüzün uygulanması için yapılan test ile aynıdır: nesnenin belirli bir yeterliliğe sahip olup olmadığına dair bir çalışma zamanı kontrolü yapıyorsunuz. Her yöntem bildirimi, yalnızca yöntem adıyla tanımlanan kendi arabirimidir ve varlığı, nesne hakkında başka herhangi bir bilgiyi ortaya çıkarmak için kullanılamaz.
IMSoP

0

Seçeneklerinin farklı motivasyon güçlerinden kaynaklandığı anlaşılıyor. Birincisi, zayıf nesne modellemesi nedeniyle kullanılan bir hack gibi görünüyor. Polimorfizmi kırdıkları için soyutlamalarınızın yanlış olduğunu gösterdi. Muhtemelen daha sıkı bir kaplinle sonuçlanan Açık kapalı prensibini kırmaz .

İkinci seçeneğiniz OOP bakış açısına göre iyi olabilir, ancak tip seçimi kafamı karıştırıyor. Eğer hesabınız şifresini sıfırlamanıza izin veriyorsa, neden bir tip tahmine ihtiyaç duyarsınız? Bu nedenle, CanResetPasswordyan tümcesinin hesabın soyutlamasının bir parçası olan temel iş gereksinimlerini temsil ettiğini varsayarsak, ikinci seçeneğiniz de sorun değil.

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.