Diğer sınıf uygulama programcıları nasıl uyarılır?


96

"Belirli bir şekilde kullanılması gereken" dersleri yazıyorum (Sanırım bütün derslerin ...).

Mesela ben bir çağrı gerektirenfooManager sınıfı yaratıyorum . Ve örneği biraz daha ileri itmek için, eylemini dinlemezsek, sınıf yararsızdır .Initialize(string,string)ThisHappened

Demek istediğim, yazdığım sınıf yöntem çağrıları gerektiriyor. Ancak bu yöntemleri çağırmazsanız ve boş bir yeni FooManager ile sonuçlanırsanız, derler. Bir noktada, sınıfa ve ne yaptığına bağlı olarak çalışmayacak veya çökmeyecektir. Sınıfımı uygulayan programcı açıkça içine bakar ve "Ah, Initialize demedim!" Diye anlardı ve iyi olurdu.

Ama bundan hoşlanmadım. İdeal olarak istediğim şey, eğer yöntem çağrılmadıysa derlenmeyecek kod; Sanırım bu mümkün değil. Ya da hemen görünür ve net olacak bir şey.

Buradaki şu anki yaklaşımdan dolayı kendimi rahatsız hissediyorum;

Sınıfta özel bir Boolean değeri ekleyin ve sınıf başlatıldıysa gereken her yeri kontrol edin; değilse, "Sınıf başlatılmadı, aradığınızdan emin misiniz .Initialize(string,string)?" yazan bir istisna atacağım .

Bu yaklaşıma karşı biraz iyiyim ama derlenen ve sonunda son kullanıcı için gerekli olmayan çok sayıda kodun oluşmasına neden oluyor.

Ayrıca, bazen Initiliazeçağrı yapmaktan daha fazla yöntem olduğunda bile daha fazla kod vardır . Sınıflarımı çok fazla kamusal yöntem / eylemle tutmamaya çalışıyorum ama bu sorunu çözmüyor, sadece mantıklı tutuyor.

Burada aradığım şey:

  • Benim yaklaşımım doğru mu?
  • Daha iyisi var mı?
  • Siz ne yapıyorsunuz / tavsiye ediyorsunuz?
  • Sorun olmayan bir sorunu çözmeye mı çalışıyorum? Meslektaşlarım tarafından, kullanmaya çalışmadan önce sınıfı kontrol etmenin programcının olduğu söylendi. Saygılarımla aynı fikirde değilim, ama bu inanıyorum.

Basitçe söylemek gerekirse, bu sınıf daha sonra tekrar kullanıldığında ya da bir başkası tarafından çağrı yapılmasını asla unutmamak için bir yol bulmaya çalışıyorum.

AÇIKLAMALAR:

Burada birçok soruyu netleştirmek için:

  • Kesinlikle sadece bir sınıfın Başlatma bölümünden bahsetmiyorum, tüm yaşam boyu. Meslektaşların bir yöntemi iki kez çağırmasını, Y'den önce X olarak adlandırdıklarından emin olmalarını sağlayın, vs. Asserts fikrini gerçekten sevdim, fakat bazı diğer fikirleri bir araya getirmem gerekeceğinden eminim, çünkü Asserts her zaman mümkün olmayacak.

  • C # dilini kullanıyorum! Bundan nasıl bahsetmedim? Xamarin ortamındayım ve genellikle PCL'ler, iOS, Android ve Windows projeleri de dahil olmak üzere bir çözümde yaklaşık 6 ila 9 proje kullanarak mobil uygulamalar geliştiriyorum. Yaklaşık bir buçuk senedir geliştiriciyim (okul ve iş birlikte), bu yüzden bazen saçma ifadelerim \ sorularım oluyor. Tüm bunlar muhtemelen burada önemli değil, ama çok fazla bilgi her zaman kötü bir şey değil.

  • Platform kısıtlamaları ve Bağımlılık Enjeksiyonunun kullanımı nedeniyle, Arayüzler dışındaki parametrelere sahip olmaları nedeniyle, yapıcıda zorunlu olan her şeyi her zaman koyamam. Ya da belki de bilgim yeterli değil, bu da mümkün. Çoğu zaman bu bir Başlatma sorunu değildir, ancak daha fazlası

O etkinliğe kaydolduğundan nasıl emin olabilirim?

"işlemi bir noktada durdurmayı" unutmadığından nasıl emin olabilirim

Burada bir İlan alma dersini hatırlıyorum. Reklamın göründüğü görünüm görünür olduğu sürece, sınıf her dakika yeni bir reklam alır. Bu sınıfın, Reklamı görüntüleyebileceği, bir parametreye açık bir şekilde girebilecek bir görünüm oluşturması gerekir. Ancak görünüm bir kez bırakıldığında, StopFetching () çağrılmalıdır. Aksi halde sınıf, orada bile olmayan bir görünüm için reklamlar almaya devam eder ve bu kötüdür.

Ayrıca, bu sınıfın, örneğin "AdClicked" gibi dinlenmesi gereken olayları vardır. Dinlenmediyse her şey yolunda gider, ancak musluklar kayıtlı değilse oradaki analiz izlemesini kaybederiz. Reklam yine de çalışır, bu nedenle kullanıcı ve geliştirici bir fark görmez ve analizler yalnızca yanlış verilere sahip olur. Bu, kaçınılması gereken, ancak geliştiricinin tao etkinliğine kaydolmaları gerektiğini nasıl bildiğinden emin değilim. Bu basitleştirilmiş bir örnek olsa da, fikir şu ki, “mevcut olan Kamu Eylemini kullandığından emin ol” ve elbette doğru zamanlarda!


13
Bir sınıfın yöntemlerine yapılan çağrıların belirli bir sırada gerçekleşmesini sağlamak genel olarak mümkün değildir . Bu, durma problemine eşdeğer birçok problemden biridir. Uyumsuzluğu derleme zamanı hatası yapmak bazı durumlarda mümkün olsa da , genel bir çözüm yoktur. Muhtemelen, az sayıda dilin, veri akışı analizi oldukça karmaşık bir hale gelse de, en azından bariz vakaları teşhis etmenize izin verecek yapıları desteklediğinin tahmin edilmesinin nedeni budur.
Kilian Foth

6

5
Diğer sorunun cevabı alakasız, soru aynı değil, bu yüzden farklı sorular. Birisi bu tartışmayı ararsa, diğer sorunun başlığını yazmaz.
Gil Sand

6
Başlangıçtaki içeriğin birleştirici içinde birleştirilmesini engelleyen nedir ? initializeNesne oluşturulduktan sonra çağrının geç yapılması gerekiyor mu? İstisna atama ve yaratma zincirini kırabilecek şekilde, taşıyıcı çok riskli olur mu?
Benekli

7
Bu soruna geçici bağlanma denir . Yapabilirseniz, geçersiz girdilerle karşılaştığınızda yapıcıya istisnalar atarak nesneyi geçersiz bir duruma getirmekten kaçınmaya çalışın; bu şekilde new, nesne kullanıma hazır değilse, aramayı asla geçemezsiniz . Bir başlatma yöntemine güvenmek, musallat olmak için geri gelmek zorundadır ve kesinlikle gerekmedikçe kaçınılması gerekir, en azından benim deneyimim bu.
16'da

Yanıtlar:


161

Bu gibi durumlarda, uygun başlatma işlemlerinde size yardımcı olmak için dilinizin tür sistemini kullanmak en iyisidir. Nasıl bir önleyebilir FooManagerolmaktan kullanılan başlatıldı olmadan? Bir engelleyerek FooManagerolmaktan oluşturulan düzgün başlatmak için gerekli bilgi olmadan. Özellikle, tüm başlangıçlar yapıcının sorumluluğundadır. Yapıcınızın yasadışı bir durumda nesne yaratmasına asla izin vermemelisiniz.

Ancak arayanların FooManagerbunu başlatabilmeleri için önce bir inşa etmeleri gerekir, çünkü örneğin FooManagerbir bağımlılık olarak geçirilir.

Eğer bir tane yoksa, yaratma FooManager. Bunun yerine yapabileceğiniz şey, etrafına tamamen inşa edilmiş , ancak yalnızca başlatma bilgileriyle erişmenizi sağlayan bir nesneyi FooManageriletmektir. (İşlevsel-programlama konuşmasında, yapıcı için kısmi uygulama kullanmanızı öneririm.) Örn:

ctorArgs = ...;
getFooManager = (initInfo) -> new FooManager(ctorArgs, initInfo);
...
getFooManager(myInitInfo).fooMethod();

Bununla ilgili sorun, her erişimde bir init bilgisini vermenizdir FooManager.

Dilinizde gerekliyse, getFooManager()işlemi fabrika veya üretici benzeri bir sınıfa sarabilirsiniz .

Gerçekten çalışma zamanı initialize()tür sistemi düzeyinde bir çözüm kullanmak yerine , yöntemin çağrıldığını kontrol etmek istiyorum .

Bir uzlaşma bulmak mümkündür. Bunu döndüren , ancak tam olarak başlatılmadıysa fırlayan MaybeInitializedFooManagerbir get()yöntemi olan bir sarmalayıcı sınıfı oluşturuyoruz . Bu yalnızca başlatma sarmalayıcıdan yapılırsa veya bir yöntem varsa işe yarar .FooManagerFooManagerFooManager#isInitialized()

class MaybeInitializedFooManager {
  private final FooManager fooManager;

  public MaybeInitializedFooManager(CtorArgs ctorArgs) {
    fooManager = new FooManager(ctorArgs);
  }

  public FooManager initialize(InitArgs initArgs) {
    fooManager.initialize(initArgs);
    return fooManager;
  }

  public FooManager get() {
    if (fooManager.isInitialized()) return fooManager;
    throw ...;
  }
}

Sınıfımın API'sini değiştirmek istemiyorum.

Bu durumda, if (!initialized) throw;her yöntemdeki koşullulardan kaçınmak isteyeceksiniz . Neyse ki, bunu çözmek için basit bir kalıp var.

Kullanıcılara sağladığınız nesne, tüm çağrıları bir uygulama nesnesine atayan boş bir kabuktur. Varsayılan olarak, uygulama nesnesi başlatılmayan her yöntem için bir hata atar. Ancak, initialize()yöntem uygulama nesnesini tamamen yapılandırılmış bir nesneyle değiştirir.

class FooManager {
  private CtorArgs ctorArgs;
  private Impl impl;

  public FooManager(CtorArgs ctorArgs) {
    this.ctorArgs = ctorArgs;
    this.impl = new UninitializedImpl();
  }

  public void initialize(InitArgs initArgs) {
    impl = new MainImpl(ctorArgs, initArgs);
  }

  public X foo() { return impl.foo(); }
  public Y bar() { return impl.bar(); }
}

interface Impl {
  X foo();
  Y bar();
}

class UninitializedImpl implements Impl {
  public X foo() { throw ...; }
  public Y bar() { throw ...; }
}

class MainImpl implements Impl {
  public MainImpl(CtorArgs c, InitArgs i);
  public X foo() { ... }
  public Y bar() { ... }
}

Bu, sınıfın ana davranışını MainImpl.


9
İlk iki çözümü (+1) severim, ancak son snippet'inizin içindeki FooManagerve içindeki yöntemleri UninitialisedImpltekrarlamasıyla tekrarlamanızdan daha iyi olduğunu sanmıyorum if (!initialized) throw;.
Bergi

3
@Bergi Bazı insanlar dersleri çok fazla seviyor. Eğer rağmen FooManagerbir sahip çok yöntemlerinin, potansiyel olarak bazı unutmadan daha kolay olabilir if (!initialized)çek. Ancak bu durumda muhtemelen sınıfı kırmayı tercih etmelisiniz.
immibis 0

1
Bir BelkiInitializedFoo da benim için ilklendirilmiş bir Foo'dan daha iyi görünmüyor, fakat bazı seçenekler / fikirler vermek için +1.
Matthew James Briggs

7
+1 Fabrika doğru seçenektir. Bir tasarımı değiştirmeden geliştiremezsiniz, bu nedenle IMHO OP'nin yardımına yardımcı olmayacağına dair cevabın son kısmı.
Nathan Cooper

6
@Bergi, son bölümde sunulan tekniği çok faydalı buldum (ayrıca bkz. “Koşulları Polimorfizm ile değiştirme” refactoring tekniği ve Durum Örüntüsü). Eğer if / else kullanırsak, kontrolü bir yöntemde unutmak kolaydır, arayüzün gerektirdiği bir yöntemi uygulamayı unutmak çok daha zordur. En önemlisi, MainImpl, initialize yönteminin yapıcı ile birleştiği bir nesneye tam olarak karşılık gelir. Bu, MainImpl'in uygulanmasının, ana kodu basitleştiren bu güçlü garantilerin tadını çıkarabileceği anlamına gelir. …
amon

79

Müşterilerin bir nesneyi "kötüye kullanmalarını" engellemenin en etkili ve yararlı yolu onu imkansız kılmaktır.

En basit çözüm, Initializeyapıcı ile birleşmektir . Bu şekilde, nesne başlatılmamış durumda istemci için hiçbir zaman kullanılamayacak, böylece hata mümkün olmayacaktır. Başlatıcıyı yapıcıda başaramazsanız, bir fabrika yöntemi oluşturabilirsiniz. Örneğin, sınıfınız belirli olayların kaydedilmesini gerektiriyorsa, olay dinleyicisini yapıcı veya fabrika yöntemi imzasında bir parametre olarak isteyebilirsiniz.

Başlatmadan önce başlatılmamış nesneye erişebilmeniz gerekiyorsa, iki durumu ayrı sınıflar olarak uygulayabilirsiniz, böylece geri dönen UnitializedFooManagerbir Initialize(...)yöntemi olan bir örnekle başlayabilirsiniz InitializedFooManager. Sadece başlangıç ​​durumunda çağrılabilen yöntemler sadece üzerindedir InitializedFooManager. Gerekirse, bu yaklaşım birden fazla duruma genişletilebilir.

Çalışma zamanı istisnalarına kıyasla, durumları farklı sınıflar olarak göstermek daha fazla işe yarar, ancak aynı zamanda derleme zamanı garantisi verir, nesne durumu için geçersiz olan yöntemleri çağırmadığınızı garanti eder ve durum geçişlerini kodda daha net belgeler.

Ancak, daha genel olarak, ideal çözüm, sınıflarınızı ve yöntemlerinizi, belirli zamanlarda ve belirli bir sırada yöntemleri çağırmanızı istemek gibi sınırlamalar olmadan tasarlamaktır. Her zaman mümkün olmayabilir, ancak birçok durumda uygun desen kullanılarak önlenebilir.

Karmaşık geçici bir birleşiminiz varsa (belirli bir sıraya göre birkaç yöntem çağrılmalıdır), bir çözüm denetimin tersini kullanmak olabilir; bu nedenle yöntemleri uygun sıraya göre çağıran ancak şablon yöntemlerini veya olaylarını kullanan bir sınıf oluşturursunuz. Müşterinin akışta uygun aşamalarda özel işlemler gerçekleştirmesine izin vermek. Bu sayede operasyonları doğru sırayla gerçekleştirme sorumluluğu müşteriden sınıfa itilir.

Basit bir örnek: Bir Filedosyadan okumanızı sağlayan bir nesne var . Ancak, istemcinin yöntem çağrılmadan Openönce araması ReadLineve sonradan yöntemin artık çağrılmaması gereken her zaman Close(bir istisna olsa bile) aramayı hatırlaması ReadLinegerekir. Bu zamansal birleşmedir. Argüman olarak bir geri çağrı veya temsilci alan tek bir yönteme sahip olmaktan kaçınılabilir. Yöntem dosyayı açmayı, geri aramayı aramayı ve sonra dosyayı kapatmayı yönetir. ReadGeri çağrıya bir yöntemle farklı bir arayüz iletebilir . Bu şekilde, müşterinin yöntemleri doğru sırayla çağırmayı unutması mümkün değildir.

Geçici kuplajlı arayüz:

class File {
     /// Must be called on a closed file.
     /// Remember to always call Close() when you are finished
     public void Open();

     /// must be called on on open file
     public string ReadLine();

     /// must be called on an open file
     public void Close();
}

Geçici bağlantı olmadan:

class File {
    /// Opens the file, executes the callback, and closes the file again.
    public void Consume(Action<IOpenFile> callback);
}

interface IOpenFile {
    string ReadLine();
}

Daha ağır bir çözüm File, açık dosya üzerinde yürütülecek (korumalı) bir yöntem uygulamanızı gerektiren soyut bir sınıf olarak tanımlamak olacaktır. Buna şablon yöntemi kalıbı denir .

abstract class File {
    /// Opens the file, executes ConsumeOpenFile(), and closes the file again.
    public void Consume();

    /// override this
    abstract protected ConsumeOpenFile();

    /// call this from your ConsumeOpenFile() implementation
    protected string ReadLine();
}

Avantaj aynıdır: Müşterinin yöntemleri belirli bir sırada çağırmayı hatırlaması gerekmez. Yöntemleri yanlış sırayla çağırmak mümkün değildir.


2
Ayrıca
Gil Sand

2
Örnek şimdi bir fabrika modelini açıklamaktadır - ancak ilk cümle aslında RAII paradigmasını tanımlamaktadır . Peki bu cevabın hangisi önermesi gerekiyor?
Ext3h

11
@ Ext3h Fabrika düzeni ve RAII'nin birbirini dışladığını sanmıyorum.
Pieter B

@ PieterB Hayır, değiller, bir fabrika RAII sağlayabilir. Ancak yalnızca şablon / yapıcının (çağrılan UnitializedFooManager) belirttiği ek ima ile , aslında semantik InitializedFooManagerolduğu gibi Initialize(...), ( ) örnekleri ile bir durumu paylaşmamalıdır Instantiate(...). Aksi takdirde, aynı şablon bir geliştirici tarafından iki kez kullanılırsa, bu örnek yeni sorunlara yol açar. Ve bu, dili moveşablonun güvenilir bir şekilde tüketilmesi için anlamsal olarak açıkça desteklememesi durumunda da statik olarak önlenmesi / doğrulanması mümkün değildir .
Ext3h

2
@ Ext3h: İlk çözümü öneririm, çünkü en basitidir. İstemci Ama eğer ihtiyaç o zaman kullanamaz, Başlat'ı çağırmadan önce nesneyi erişmek mümkün, ancak ikinci bir çözüm kullanmak mümkün olabilir.
JacquesB

39

Normalde sadece başlangıç ​​durumuna getirme olup olmadığına bakardım ve IllegalStateExceptioneğer başlamazken onu kullanırsanız bir tane söylerdim .

Bununla birlikte, derleme zamanı güveninde olmak istiyorsanız (ve bu hak kazanılabilir ve tercih edilirse), niçin yapılandırmayı inşa edilmiş ve başlatılmış bir nesneyi döndüren bir fabrika yöntemi olarak başlatmaya çalışmayın?

ComponentBuilder builder = new ComponentBuilder();
Component forClients = builder.initialise(); // the 'Component' is what you give your clients

ve böylece nesnelerin oluşturulmasını ve yaşam döngüsünü kontrol edersiniz ve müşterileriniz Componentbaşlangıç ​​işleminizin sonucunu alır. Etkili tembel bir örnekleme.


1
İyi cevap - güzel ve özlü bir cevapta 2 iyi seçenek. Yukarıdaki diğer cevaplar (teorik olarak geçerli olan) tip sistemli jimnastik; Ancak, çoğu durumda, bu 2 basit seçenek ideal pratik seçimlerdir.
Thomas W,

1
Not: .NET'te, InvalidOperationException , Microsoft tarafından sizin aradığınız şekilde tanıtılır IllegalStateException.
miroxlav

1
Bu cevabı küçümsemiyorum ama bu gerçekten iyi bir cevap değil. Sadece atarak IllegalStateExceptionveya InvalidOperationExceptionmavi dışarı, bir kullanıcı yanlış yaptıklarını asla anlamayacak. Bu istisnalar, tasarım hatasını düzeltmek için bir baypas olmamalıdır.
displayName

İstisna atmanın olası bir seçenek olduğunu ve tercih ettiğim seçeneği daha da geliştireceğimi ve bu derleme zamanını güvenli hale getirdiğimi not edersiniz. Bunu vurgulamak için cevabımı
düzelttim

14

Diğer cevaplardan biraz koparacağım ve katılmıyorum: hangi dilde çalıştığınızı bilmeden cevap vermek imkansız. Bunun yararlı olup olmadığını ve vereceğiniz doğru bir "uyarı" olup olmadığını kullanıcılarınız tamamen dilinizin sağladığı mekanizmalara ve o dilin diğer geliştiricilerinin takip etme eğilimindeki sözleşmelere bağlıdır.

Eğer bir varsa FooManagerHaskell, olurdu ceza kullanıcıların yönetebilen olmayan birinin oluşması bir Footür sistemi o kadar kolay hale getirir ve bu Haskell geliştiriciler bekliyoruz kuralları, çünkü s.

Öte yandan, C yazarsanız, iş arkadaşlarınız sizi geri alma ve farklı işlemleri destekleyen ayrı struct FooManagerve struct UninitializedFooManagertürler tanımlamanız için sizi vurma hakları dahilinde olacaklardır , çünkü çok az fayda için gereksiz yere karmaşık kodlar ortaya çıkacaktır. .

Python yazıyorsanız, bunu yapmanıza izin verecek dilde bir mekanizma yoktur.

Muhtemelen Haskell, Python veya C yazmıyorsunuzdur, ancak bunlar tip sisteminin ne kadar / ne kadar az iş yapması beklendiği ve yapabileceği şeyler açısından açıklayıcı örneklerdir.

Kendi dilinizde bir geliştiricinin makul beklentileri izleyin ve doğal, deyimsel uygulaması olmaması bir çözüm mühendisi üzerinde güdüsünden hata o aşırı giderek değer olduğunu yakalamak için yapmak kadar kolay ve çok zor olmadığı sürece ( imkansız kılmak için uzunlukları). Dilinizde makul olanı değerlendirmek için yeterli deneyime sahip değilseniz, onu sizden daha iyi tanıyan birisinin tavsiyesine uyun.


Kafam karıştı, "Python yazıyorsanız, dilde bunu yapmanıza izin veren bir mekanizma yok." Python'un kurucuları var ve fabrika metotları oluşturmak oldukça önemsiz. Öyleyse belki de "bu" derken ne demek istediğini anlamadım.
AL Flanagan

3
"Bu" derken, bir çalışma zamanının aksine derleme zamanı hatası demek istedim.
Patrick Collins,

Sadece şu anki versiyonun takılabilir tipte bir sistem üzerinden yazdığı Python 3.5 'ten bahsetmek yeterli. Python'u yalnızca kodu içe aktarıldığından kod çalıştırmadan önce hatayla almak mümkündür. 3.5'e kadar tamamen olsa doğruydu.
Benjamin Gruenbaum,

Ah tamam. Gecikmiş +1. Kafası karışık olsa bile, bu çok anlayışlıydı.
AL Flanagan

Tarzını beğeniyorum Patrick.
Gil Sand,

4

Kod kontrolünü müşteriye göndermek istemiyormuş gibi görünüyorsunuz (ancak programcı için bunu yapmak gayet iyi görünüyor) programlama dilinizde mevcutsa, assert işlevlerini kullanabilirsiniz .

Bu şekilde geliştirme ortamındaki kontrollere sahip olursunuz (ve bir dostun WILL olarak tahmin edeceği herhangi bir testte tahmin edilebilir şekilde başarısız olur), ancak kodu müşteriye göndermezsiniz (en azından Java'da) sadece seçmeli olarak derlenir.

Dolayısıyla, bunu kullanan bir Java sınıfı şöyle görünür:

/** For some reason, you have to call init and connect before the manager works. */
public class FooManager {
   private int state = 0;

   public FooManager () {
   }

   public synchronized void init() {
      assert state==0 : "you called init twice.";
      // do something
      state = 1;
   }

   public synchronized void connect() {
      assert state==1 : "you called connect before init, or connect twice.";
      // do something
      state = 2;
   }

   public void someWork() {
      assert state==2 : "You did not properly init FooManager. You need to call init and connect first.";
      // do the actual work.
   }
}

İddialar, ihtiyaç duyduğunuz programınızın çalışma zamanını kontrol etmek için harika bir araçtır, ancak gerçekte gerçek bir ortamda yanlış yapan kimseyi beklemeyin.

Ayrıca oldukça incedirler ve if () atmak ... sözdizimi, yakalanmaları, vb.


3

Bu soruna genel olarak şu anki cevaplardan daha fazla bakmak, çoğunlukla ilk kullanıma odaklanmaktaydı. İki yöntem olacaktır bir nesne düşünün, a()ve b(). Gereksinim, a()her zaman önce denir olmasıdır b(). Yeni bir nesneyi orijinal nesneyi yerine geri getirip yeni nesneye a()taşıyarak bunun bir derleme zamanı kontrolü oluşturabilirsiniz b(). Örnek uygulama:

class SomeClass
{
   private int valueRequiredByMethodB;

   public IReadyForB a (int v) { valueRequiredByMethodB = v; return new ReadyForB(this); }

   public interface IReadyForB { public void b (); }

   private class ReadyForB : IReadyForB
   {
      SomeClass owner;
      private ReadyForB (SomeClass owner) { this.owner = owner; }
      public void b () { Console.WriteLine (owner.valueRequiredByMethodB); }
   }
}

Şimdi, bir () çağrılıncaya kadar gizlenmiş bir arabirimde uygulandığından, önce bir () çağırmadan b () öğesini çağırmak mümkün değildir. Kuşkusuz, bu oldukça fazla çaba, bu yüzden genellikle bu yaklaşımı kullanmazdım, ancak bunun yararlı olabileceği durumlar var (özellikle sınıfınız, programcıların kullanamayacağı programcılar tarafından birçok durumda tekrar kullanılacaksa) uygulanmasına aşina olmak veya güvenilirliğin kritik olduğu kodlar çerçevesinde). Ayrıca, mevcut cevapların birçoğunun önerdiği gibi, bu oluşturucu şablonunun bir genellemesi olduğuna dikkat edin. Neredeyse aynı şekilde çalışır, tek gerçek fark, verilerin depolandığı (geri döndürülenden ziyade orijinal nesnede) ve ne zaman kullanılması gerektiğidir (yalnızca başlatma sırasında herhangi bir zamanda).


2

Ek başlatma bilgisine ihtiyaç duyan veya yararlı olmadan önce diğer nesnelere bağlantılar gerektiren bir temel sınıf uyguladığımda, bu temel sınıfı soyut yapma eğilimindeyim, daha sonra bu sınıfta temel sınıfın akışında kullanılan birkaç soyut yöntemi tanımlarım (gibi abstract Collection<Information> get_extra_information(args);ve abstract Collection<OtherObjects> get_other_objects(args);miras sözleşmesinin somut sınıf tarafından uygulanması zorunludur ve bu baz sınıfın kullanıcısını, baz sınıfın gerektirdiği her şeyi sağlamaya zorlar.

Bu yüzden, temel sınıfı uyguladığımda, temel sınıfın doğru davranmasını sağlamak için ne yazmam gerektiğini derhal ve açıkça biliyorum; çünkü sadece soyut yöntemleri uygulamam gerekiyor ve hepsi bu.

EDIT: açıklığa kavuşturmak için bu, temel sınıfın kurucusuna argümanlar sağlamakla aynıdır, ancak soyut yöntem uygulamaları, uygulamanın soyut yöntem çağrılarına iletilen argümanları işlemesine izin verir. Veya hiç bir argüman kullanılmadığında bile, soyut yöntemin dönüş değerinin, değişken değişkenini argüman olarak geçirirken mümkün olmayan, yöntem gövdesinde tanımlayabileceğiniz bir duruma bağlı olması yararlı olabilir. Oluşturucu Miras üzerinde bir kompozisyon kullanmayı tercih ederseniz, aynı prensibe dayanan dinamik davranışa sahip bir tartışmayı yine de iletebilirsiniz.


2

Cevap: Evet, hayır ve bazen. :-)

Tarif ettiğiniz sorunlardan bazıları, en azından çoğu durumda, kolayca çözülür.

Derleme zamanı denetimi kesinlikle hiç çalışma yapmaması tercih edilen çalışma zamanı denetimine tercih edilir.

RE çalışma zamanı:

Çalışma zamanı kontrolleriyle fonksiyonların belli bir sıra ile çağrılmasını zorlamak en azından kavramsal olarak kolaydır. Sadece hangi işlevin çalıştığını söyleyen bayraklar koyun ve her işlevin "ön-koşul-1 değilse veya ön-koşul-2 değilse istisna atma" gibi bir şeyle başlamasına izin verin. Çekler karmaşıklaşırsa, onları özel bir işleve sokabilirsiniz.

Bazı şeyleri otomatik olarak gerçekleştirebilirsiniz. Basit bir örnek almak gerekirse, programcılar genellikle bir nesnenin "tembel popülasyonu" hakkında konuşurlar. Örnek oluşturulduğunda, doldurulmuş bir bayrağını false olarak veya bazı anahtar nesne başvurusunu null olarak ayarlarsınız. Daha sonra ilgili verilere ihtiyaç duyan bir fonksiyon çağrıldığında bayrağı kontrol eder. Değer yanlışsa, verileri doldurur ve bayrağı true olarak ayarlar. Doğruysa, verinin orada olduğunu varsayarak devam eder. Aynı şeyi diğer önkoşullarla da yapabilirsiniz. Yapıcıda veya varsayılan değer olarak bir bayrak ayarlayın. Sonra, bazı ön-koşul fonksiyonlarının çağrılması gereken bir noktaya ulaştığınızda, eğer henüz çağrılmadıysa, onu arayın. Tabii ki bu sadece, bu noktada çağıracak verilere sahipseniz işe yarar, ancak çoğu durumda, "işlev parametrelerine gereken verileri ekleyebilirsiniz

RE derleme zamanı:

Diğerlerinin söylediği gibi, bir nesneyi kullanmadan önce başlatmanız gerekirse, bu başlatıcıyı kurucuya yerleştirir. Bu, OOP için oldukça tipik bir tavsiyedir. Mümkünse, yapıcıya nesnenin geçerli, kullanılabilir bir örneğini oluşturmasını sağlamalısınız.

İlklendirmeden önce nesneye referansları aktarmanız gerekirse, ne yapılması gerektiği hakkında bir tartışma gördüm. Bunun kafamın üstündeki gerçek bir örneğini düşünemiyorum, ama sanırım olabilir. Çözümler önerildi, ancak burada gördüğüm ve düşünebildiğim çözümler dağınık ve kodları karmaşıklaştırıyor. Sormanız gereken bir noktada, çalışma zamanı kontrolü yerine derleme zamanı kontrolü yapabilmemiz için çirkin, anlaşılması zor bir kod oluşturmaya değer mi? Yoksa kendime ve başkalarına güzel ama gerekli olmayan bir amaç için çok çalışıyorum?

İki fonksiyon her zaman birlikte çalıştırılmalıysa, basit çözüm onları bir fonksiyon haline getirmektir. Bir sayfaya her reklam eklediğinizde de bunun için bir metrik işleyici eklemelisiniz, ardından metrik işleyicisini "reklam ekle" işlevinin içine eklemek için kodu koyun.

Derleme zamanında bazı şeyleri kontrol etmek neredeyse imkansız olurdu. Belli bir işlevin bir kereden fazla çağrılamaması gibi bir gereklilik gibi. (Bunun gibi birçok fonksiyon yazdım. Yakın zamanda sihirli bir dizindeki dosyaları arayan, bulduğu işlemleri yapan ve sonra bunları silen bir fonksiyon yazdım. Tabii ki dosyayı sildikten sonra yeniden çalıştırılması mümkün değildir. Vb.) Derleme zamanında aynı anda bir işlevin iki kez çağrılmasını engellemenizi sağlayan bir özelliği olan hiçbir dil bilmiyorum. Derleyicinin bunun nasıl olduğunu kesin olarak nasıl çözebileceğini bilmiyorum. Yapabileceğin tek şey çalışma zamanı kontrollerini koymak. Özellikle çalışma zamanı kontrolü açıktır, değil mi? "Eğer zaten burada olduysa = true, daha sonra zaten ayarlanmış olan istisnaları at.

RE'yi uygulamak imkansız

Bir sınıfın işiniz bitince bir çeşit temizlik gerektirmesi nadir değildir: dosyaları veya bağlantıları kapatmak, belleği boşaltmak, DB'ye nihai sonuçlar yazmak, vb. veya çalışma zamanı. Bence çoğu OOP dili, bir örnek çöp toplandığında çağrılan bir "sonlandırıcı" işlevi için bir çeşit hükme sahip olduğunu düşünüyorum. OOP dilleri genellikle, bir örneğin kullanımına, "kullanma" ya da "birlikte" yan tümcesine ve programın kullanım kapsamından çıktığı bir programın kullanımına ilişkin bir kapsam belirlemek için bir tür hükme sahip "dispose" fonksiyonunu içerir. Ancak bu, programcının uygun kapsam belirticisini kullanmasını gerektirir. Programcıyı doğru şekilde yapmaya zorlamaz,

Programcıyı sınıfı doğru kullanmaya zorlamanın imkansız olduğu durumlarda, yanlış sonuçlar vermek yerine yanlış yaparsa programın patlamasını sağlamaya çalışırım. Basit bir örnek: Değerleri aldatmak için bir sürü veriyi başlatan birçok program gördüm, böylece kullanıcı verileri doğru şekilde yerleştirmek için işlevleri çağırmasa bile, hiçbir zaman boş gösterici istisnası almayacak. Ve her zaman nedenini merak ediyorum. Böcekleri bulmayı zorlaştırmak için yolundan çıkıyorsun. Bir programcı "veri yükle" işlevini çağıramadığında ve ardından veriyi kullanmaya çalıştığında, boş bir işaretçi istisnası aldıysa, istisna, sorunun nerede olduğunu hemen gösterecektir. Ancak, bu gibi durumlarda verileri boş veya sıfır yaparak gizlerseniz, program tamamlanmaya çalışabilir ancak yanlış sonuçlar verebilir. Bazı durumlarda, programcı bir sorun olduğunu bile fark edemeyebilir. Eğer farkederse, nerede yanlış olduğunu bulmak için uzun bir yol izlemesi gerekebilir. Cesaretle devam etmeye çalışmaktan daha erken başarısız olmaktan iyidir.

Genel olarak, elbette, bir nesneyi yanlış kullanmak imkansız hale geldiğinde her zaman iyidir. İşlevler, programcının geçersiz aramalar yapmasına izin vermeyecek şekilde yapılandırılmışsa veya geçersiz çağrılar derleme zamanı hataları oluşturuyorsa iyidir.

Ancak, söylemeniz gereken bir nokta var. Programcı, fonksiyon hakkındaki belgeleri okumalıdır.


1
Temizlemeye zorlama ile ilgili olarak, bir kaynağın yalnızca belirli bir yöntemi çağırarak tahsis edilebileceği bir kalıp vardır (örneğin, tipik bir OO dilinde özel bir kurucuya sahip olabilir). Bu yöntem kaynağı tahsis eder, kaynağa referansla kendisine iletilen bir işlevi (veya bir arabirimin yöntemini) çağırır ve sonra bu işlev geri döndüğünde kaynağı yok eder. Kesintisiz iş parçacığının sonlandırılması dışında, bu şablon kaynağın her zaman imha edilmesini sağlar. İşlevsel dillerde yaygın bir yaklaşımdır, ancak OO sistemlerinde iyi etki için kullanıldığını da gördüm.
Jules

@Jules aka "disposers"
Benjamin

2

Evet, içgüdülerin belli. Yıllar önce Debugging , Abugging ve Antibugging konularını içeren dergilerde yazılar yazdım (burası gibi, ancak ölü ağaçlar üzerine ve ayda bir kez) .

Derleyicinin kötüye kullanımı yakalamasını sağlayabilirseniz, bu en iyi yoldur. Hangi dil özelliklerinin mevcut olduğu dile bağlıdır ve siz belirtmediniz. Zaten bu sitede kişisel sorular için özel teknikler ayrıntılı olarak açıklanacaktır.

Ancak, çalışma zamanı yerine derleme zamanında kullanımı saptamak için bir test yapmak gerçekten aynı tür bir testtir: yine de önce doğru işlevleri çağırıp doğru şekilde kullanmanız gerektiğini bilmeniz gerekir.

Bileşeni ilk etapta bir sorun olmaktan bile kaçınmak için tasarlamak çok daha belirsizdir ve Buffer'ı Element örneği gibi hissediyorum . Bölüm 4 (yirmi yıl önce yayınlandı! Vay!) Davanıza oldukça benzeyen bir tartışma örneğini içeriyor: Bu sınıf dikkatli kullanılmalıdır, çünkü (() read () kullanmaya başladıktan sonra buffer () çağrılmayabilir. Aksine, inşaattan hemen sonra sadece bir kez kullanılmalıdır. Sınıfın arayana otomatik olarak ve görünmeden arabellek yönetmesi, kavramsal olarak sorunu önleyecektir.

Doğru kullanımı sağlamak için çalışma zamanında test yapılabilir mi diye soruyorsunuz, değil mi?

Evet derdim . Kodunuzu koruduğunuzda ve belirli kullanım bozulduğunda, ekibinize bir gün büyük miktarda hata ayıklama işi kazandırır. Test, test edilebilen resmi bir kısıtlamalar ve değişmezler kümesi olarak ayarlanabilir . Bu, durumu izlemek için fazladan alanın eklenmesi anlamına gelmez ve kontrolü yapmak için çalışmak her çağrı için her zaman açık kalır. O yüzden sınıfta var olabilir çağrılacak ve kod fiili kısıtları ve varsayımları belgeler.

Belki de kontrol sadece hata ayıklama yapısında yapılır.

Belki çek (örneğin, heapcheck düşünün) karmaşıktır, ama bu mevcut ve arada bir yapılabilir, veya kod üzerinde çalışılan ve bazı sorun su yüzüne ya özgü testler yapmak zaman çağrılar oraya buraya eklenen aynı sınıfa bağlanan ünite testi veya entegrasyon testi programında böyle bir problem olmadığından emin olun .

Her şeyden önce ek yükün önemsiz olduğuna karar verebilirsiniz. Eğer sınıf G / Ç dosyası yapar ve ekstra durum baytı ve bunun için bir test yapılmaz. Yaygın iostream sınıfları, akışın kötü durumda olup olmadığını kontrol eder.

İçgüdüleriniz iyi. Aynen böyle devam!


0

Bilgi Gizleme (Kapsülleme) ilkesi, sınıf dışındaki varlıkların bu sınıfı doğru kullanmak için gerekenden daha fazlasını bilmemesi gerektiğidir.

Davanız, dış kullanıcılara sınıf hakkında yeterince bilgi vermeden düzgün çalışması için bir sınıfın nesnesini almaya çalışıyor gibi görünüyor. Olması gerekenden daha fazla bilgi gizlediniz.

  • Her ikisi de açıkça kurucuda gerekli bilgileri isteyin;
  • Veya yapıcıları sağlam tutun ve yöntemleri çağırmak için eksik verileri sağlamanız için yöntemleri değiştirin.

Özlü söz:

* Her iki şekilde de sınıfı ve / veya yapıcı ve / veya yöntemleri yeniden tasarlamanız gerekir.

* Düzgün tasarlamamış ve sınıfı doğru bilgilerden aç bırakarak, sınıfınızı bir değil potansiyel olarak birden fazla yerde kırma riskini alıyorsunuz.

* İstisnaları ve hata mesajlarını kötü bir şekilde yazdıysanız, sınıf kendiniz hakkında daha fazla bilgi verecektir.

* Son olarak, dışarıdan birinin a, b ve c'yi başlatması gerektiğini bilmesi ve sonra d (), e (), f (int) çağrısı yapması gerekiyorsa, soyutlamanızda bir sızıntı var demektir.


0

C # kullandığınızı belirttiğinize göre, durumunuza uygun bir çözüm Roslyn Kod Analizörleri'ni kullanmaktır . Bu, derhal ihlalleri yakalayabilmenizi ve hatta kod düzeltmelerini önerebilmenizi sağlayacaktır .

Bunu gerçekleştirmenin bir yolu, geçici olarak birleştirilmiş yöntemleri, yöntemlerin 1 olarak adlandırılması gereken sırayı belirten bir öznitelikle dekore etmektir . Analizör bu özellikleri bir sınıfta bulduğunda, yöntemlerin sırayla çağrıldığını doğrular. Bu, sınıfınızın aşağıdaki gibi görünmesini sağlar:

public abstract class Foo
{
   [TemporallyCoupled(1)]
   public abstract void Init();

   [TemporallyCoupled(2)]
   public abstract void DoBar();

   [TemporallyCoupled(3)]
   public abstract void Close();
}

1: Asla bir Roslyn Kod Analizörü yazmadım, bu yüzden bu en iyi uygulama olmayabilir. API'nizin doğru kullanıldığını doğrulamak için Roslyn Code Analyzer'ı kullanma fikri% 100 sese rağmen.


-1

Bu sorunu daha önce çözdüğüm yol MakeFooManager(), sınıf içinde özel bir kurucu ve statik bir yöntemdi. Örnek:

public class FooManager
{
     public string Foo { get; private set; }
     public string Bar { get; private set; }

     private FooManager(string foo, string bar)
     {
         Foo = foo;
         Bar = bar;
     }

     public static FooManager MakeFooManager(string foo, string bar)
     {
         // Can do other checks here too.
         if(foo == null || bar == null)
         {
             return null;
         }
         else
         {
             return new FooManager(foo, bar);
         }
     }
}

Bir kurucu uygulandığı için, hiç kimsenin FooManagergeçmeden bir örnek oluşturması mümkün değildir MakeFooManager().


1
Bu sadece birkaç saat önce gönderilen bir önceki cevapta yapılan ve açıklanan noktayı tekrar ediyor gibi görünüyor
gnat

1
Bunun tek başına boşta kontrol etmenin hiçbir avantajı yok mu? Başka bir yöntemi silmekten başka hiçbir şey yapmayan bir yöntem yaptınız. neden?
sara,

Ayrıca neden foo ve bar üyesi değişkenleri var?
Patrick M,

-1

Birkaç yaklaşım var:

  1. Sınıfın kullanımı kolay ve yanlış kullanılması zor. Diğer cevapların bazıları sadece bu noktaya odaklanır, bu yüzden RAII ve oluşturucu düzeninden bahsedeceğim .

  2. Yorumların nasıl kullanılacağına dikkat edin. Eğer sınıf kendini açıklıyorsa, yorum yazmayın. * Bu şekilde, sınıf gerçekten ihtiyaç duyduğunda insanların yorumlarınızı okuması daha olasıdır. Ve bir sınıfın doğru kullanımı zor ve yorum gerektiren bu nadir durumlardan birine sahipseniz, örnekler ekleyin.

  3. Hata ayıklama yapılarınızdaki varsayımları kullanın .

  4. Sınıfın kullanımı geçersizse istisnalar atın.

* Bunlar yazmamanız gereken yorumlar.

// gets Foo
GetFoo();
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.