Fonksiyonların belirli bir sırada çağrılması gereken arayüz tasarımı


24

Görev, bazı giriş özelliklerine göre cihaz içindeki bir donanımı yapılandırmaktır. Bu, aşağıdaki gibi gerçekleştirilmelidir:

1) Yapılandırma bilgilerini toplayın. Bu farklı zamanlarda ve yerlerde olabilir. Örneğin, modül A ve modül B hem modülümden bazı kaynaklar (farklı zamanlarda) talep edebilir. Bu 'kaynaklar' aslında yapılandırmanın ne olduğudur.

2) Daha fazla isteğin gerçekleşmeyeceği anlaşıldıktan sonra, istenen kaynakların bir özetini veren bir başlangıç ​​komutunun donanıma gönderilmesi gerekir.

3) Ancak bundan sonra söz konusu kaynakların detaylı bir şekilde yapılandırılması yapılabilir (ve gerekir).

4) Ayrıca, yalnızca 2) 'den sonra, seçilen kaynakların bildirilen arayanlara yönlendirilmesi (ve yapılması gerekir).


Hataların ortak bir nedeni, benim için bile, bir şeyi yazan, bu emri yanlıştır. Arabirimi ilk kez kodu gören biri tarafından kullanılabilir duruma getirmek için hangi adlandırma kuralları, tasarımları veya mekanizmaları kullanabilirim?


1. Aşama daha iyi discoveryya da handshake?
rwong

1
Geçici kuplaj bir anti-paterndir ve bundan kaçınılmalıdır.

1
Sorunun başlığı, adım oluşturucu düzeniyle ilgilenebileceğinizi düşündürüyor .
Joshua Taylor

Yanıtlar:


45

Yeniden tasarlandı, ancak birçok API'nin kötüye kullanılmasını önleyebilirsiniz ancak çağrılmaması gereken herhangi bir yöntem kullanılamaz.

Örneğin, yerine first you init, then you start, then you stop

Yapıcınız initbaşlatılabilecek bir nesnedir ve startdurdurulabilen bir oturum oluşturur.

Elbette bir seferde bir seansta kısıtlama varsa, birinin zaten aktif olan birini yaratmaya çalıştığı durumu ele almanız gerekir.

Şimdi bu tekniği kendi davanıza uygulayın.


zlibve jpeglibbaşlatma için bu modeli takip eden iki örnektir. Yine de, konsepti geliştiricilere öğretmek için birçok belge gereklidir.
rwong

5
Bu tam olarak doğru cevap: eğer sipariş önemlidir, her fonksiyon bir sonraki adımı gerçekleştirmek için çağrılabilecek bir sonuç verir. Derleyicinin kendisi tasarım kısıtlarını uygulayabilir.

2
Bu, adım oluşturucu modeline benzer ; sadece belirli bir aşamada anlamlı olan arayüzü sunar.
Joshua Taylor

@JoshuaTaylor cevabım bir adım oluşturucu kalıp uygulamasıdır :)
Silviu Burcea

@SilviuBurcea Cevabınız bir adım oluşturucu uygulaması değildir, ancak burada değil, bunun hakkında yorum yapacağım.
Joshua Taylor

19

Başlangıç ​​yönteminin, yapılandırmaya gerekli bir parametre olan bir nesneyi döndürmesini sağlayabilirsiniz:

Kaynak * MyModule :: GetResource ();
MySession * MyModule :: Startup ();
void Resource :: Configure (MySession * oturumu);

Sizin MySessionyalnızca boş bir yapı olsa bile , bu Configure(), başlatmadan önce hiçbir yöntemin çağrılmayacağı tür güvenliği ile uygulanacaktır .


Birinin yapmasını engelleyen ne module->GetResource()->Configure(nullptr)?
svick

@svick: Hiçbir şey, ama açıkça bunu yapmalısın. Bu yaklaşım ne beklediğini ve bu beklentinin bilinçli bir karar olduğunu atlayarak size söyler. Çoğu programlama dilinde olduğu gibi, hiç kimse kendinizi ayağınızdan vurmanızı engellemez. Ancak bir API tarafından bunu yaptığınızı açıkça belirtmek her zaman iyidir;)
Michael Klement

+1 harika ve basit görünüyor. Ancak bir problem görebiliyorum. Nesnelerim varsa a, b, c, d, başlayabilirim ave bunu daha önce başlatılmış bir nesne olarak MySessionkullanmayı denemek için kullanıyorum b, gerçekte değil.
Vorac

8

Cashcow Cevabı Üzerine İnşa Etmek - neden yeni bir Arayüz sunabiliyorken, neden arayan kişiye yeni bir Obje sunmak zorundasınız? Rebrand-Desen:

class IStartable     { public: virtual IRunnable      start()     = 0; };
class IRunnable      { public: virtual ITerminateable run()       = 0; };
class ITerminateable { public: virtual void           terminate() = 0; };

Bir oturum birden çok kez çalıştırılabilirse, ITerminateable uygulamasının IRunnable uygulamasını da sağlayabilirsiniz.

Nesneniz:

class Service : IStartable, IRunnable, ITerminateable
{
  public:
    IRunnable      start()     { ...; return this; }
    ITerminateable run()       { ...; return this; }
    void           terminate() { ...; }
}

// And use it like this:
IStartable myService = Service();

// Now you can only call start() via the interface
IRunnable configuredService = myService.start();

// Now you can also call run(), because it is wrapped in the new interface...

Bu yolla yalnızca doğru yöntemleri çağırabilirsiniz, çünkü başlangıçta yalnızca IStartable-Arabiriminiz vardır ve run () yöntemine yalnızca start () işlevini çağırdığınızda erişilebilir. Dışarıdan, birden fazla sınıf ve Nesne içeren bir kalıp gibi görünür, fakat temel sınıf her zaman referans verilen bir sınıf kalır.


1
Birkaç yerine sadece bir temel sınıfa sahip olmanın avantajı nedir? Bu benim önerdiğim çözümün tek farkı olduğu için, bu özel noktaya ilgi duyardım.
Michael Le Barbier Grünewald

1
@ MichaelGrünewald Tüm arayüzleri tek bir sınıfla uygulamak gerekli değildir, ancak konfigürasyon tipi bir nesne için, arayüzlerin örnekleri arasındaki verileri paylaşmak en basit uygulama tekniği olabilir (yani aynı olması nedeniyle paylaşıldığı için nesne).
Joshua Taylor


@JoshuaTaylor Arabirimin örnekleri arasında veri paylaşımı iki yönlüdür: uygulanması daha kolay olsa da, "tanımsız duruma" erişmemeye dikkat etmeliyiz (bağlı olmayan bir sunucunun müşteri adresine erişme gibi). OP, arayüzün kullanılabilirliğine vurgu yaptığında, iki yaklaşımı eşit olarak değerlendirebiliriz. “Adım oluşturucu deseni” BTW'den alıntı yaptığınız için teşekkür ederiz.
Michael Le Barbier Grünewald

1
@ MichaelGrünewald Nesne ile yalnızca belirli bir noktada belirtilen belirli arabirim aracılığıyla etkileşime giriyorsanız, bu duruma erişmenin hiçbir yolu (döküm vb. Olmadan) olmamalıdır.
Joshua Taylor

2

Sorununuzu çözmek için birçok geçerli yaklaşım var. Basile Starynkevitch, sizi basit bir ara yüzle bırakan ve uygun şekilde ara yüz kullanarak programcıya güvenen bir “sıfır bürokrasi” yaklaşımı önerdi. Bu yaklaşımı sevmeme rağmen, daha fazla hareketli olan ancak derleyicinin bazı hataları yakalamasını sağlayan başka bir tane daha sunacağım.

  1. Cihazınız olarak, olabilir, çeşitli durumları belirleyin Uninitialised, Started, Configuredvb. Liste sonlu olmalı.

  2. Her eyalet için, structbu durumla ilgili gerekli ek bilgileri, örneğin DeviceUninitialised, DeviceStartedvs.

  3. Tüm tedavileri, DeviceStrategyyöntemlerde girdi ve çıktı olarak tanımlanan 2. yapıların kullanıldığı tek bir nesnede paketleyin . Bu nedenle, bir DeviceStarted DeviceStrategy::start (DeviceUninitalised dev)yönteminiz olabilir (veya eşdeğeriniz ne olursa olsun proje sözleşmelerinize göre olabilir).

Bu yaklaşımla, geçerli bir program, yöntem prototipleri tarafından uygulanan dizideki bazı yöntemleri çağırmalıdır.

Çeşitli durumlar alakasız nesnelerdir, bu ikame prensibinden dolayıdır. Bu yapıların ortak bir atayı paylaşması sizin için yararlısa, ziyaretçi modelinin soyut bir sınıf örneğinin somut türünü kurtarmak için kullanılabileceğini hatırlayın.

Eşsiz bir DeviceStrategysınıfta 3. tanımladığım halde, sağladığı işlevselliği birkaç sınıfa bölmek isteyebileceğiniz durumlar vardır.

Bunları özetlemek için, tarif ettiğim tasarımın kilit noktaları:

  1. İkame prensibi nedeniyle, aygıt devletlerini temsil eden nesneler farklı olmalı ve özel miras ilişkilerine sahip olmamalıdır.

  2. Aygıt tedavilerini aygıtların kendisini temsil eden nesneler yerine başlangıç ​​nesnelerine paketleyin, böylece her aygıt veya aygıt durumu yalnızca kendisini görür ve strateji hepsini görür ve aralarındaki olası geçişleri ifade eder.

Bu satırları izleyen bir telnet istemcisi uygulamasının bir tanımını gördüğüme yemin ederim, ancak bir daha bulamadım. Çok faydalı bir referans olurdu!

¹: Bunun için sezginizi takip edin veya “yöntem” yöntemi ilişkisine ilişkin gerçek uygulamanızda eşdeğerlik sınıflarını bulun. Onları aynı nesnede kullanmak geçerlidir ”- cihazınızdaki tüm tedavileri içine alan büyük bir nesneniz olduğunu varsayalım. Her iki devlet de listeleme yöntemi harika sonuçlar verir.


1
Ayrı yapıları tanımlamak yerine, her aşamada bir nesnenin göstermesi gereken gerekli arayüzleri tanımlamak yeterli olabilir. O zaman adım oluşturucu deseni .
Joshua Taylor

2

Bir oluşturucu deseni kullanın.

Yukarıda belirtilen tüm işlemler için yöntemleri olan bir nesneye sahip olun. Ancak bu işlemleri hemen gerçekleştirmez. Sadece her bir işlemi daha sonra hatırlar. İşlemler hemen gerçekleştirilmediğinden, bunları oluşturucuya verdiğiniz sıra önemli değildir.

Oluşturucudaki tüm işlemleri tanımladıktan sonra, bir execute-metod çağırırsınız. Bu yöntem çağrıldığında, yukarıda listelenen tüm adımları yukarıda kaydettiğiniz işlemlerle doğru sırada yerine getirir. Bu yöntem aynı zamanda donanıma yazmaya başlamadan önce bazı operasyonel akıl sağlığı kontrolleri yapmak için (henüz kurulmamış bir kaynağı yapılandırmaya çalışmak gibi) iyi bir yerdir. Bu, cihaza saçma sapan bir yapılandırma ile zarar vermekten kurtarabilir (donanımınızın buna duyarlı olması durumunda).


1

Arayüzün nasıl kullanıldığını doğru bir şekilde belgelemeniz ve öğretici bir örnek vermeniz yeterli.

Bazı çalışma zamanı kontrolleri yapan bir hata ayıklama kitaplığı değişkenine de sahip olabilirsiniz.

Belki tanımlanması ve doğru olarak bazı adlandırma kurallarına belgeleyen (örneğin preconfigure*, startup*, postconfigure*, run*....)

BTW, bir çok mevcut arayüz benzer bir kalıbı izler (örneğin, X11 araç setleri).


Bilgiyi iletmek için Android uygulama etkinliği yaşam döngüsüne benzer bir durum geçiş diyagramı gerekebilir.
rwong

1

Bu gerçekten yaygın ve sinsi bir hatadır, çünkü derleyiciler sadece sözdizimi koşullarını uygulayabilir, bununla birlikte müşteri programlarınızın "gramer" in doğru olmasını istersiniz.

Ne yazık ki, adlandırma kuralları bu tür hatalara karşı neredeyse tamamen etkisizdir. Eğer gerçekten kuralsız şeyler yapmak değil teşvik etmek isterseniz, onlar böylece, ön koşulları için değerlerle ilklendirilmelidir çeşit komut nesneyi geçmelidir olamaz bozuk adımları uygulayın.


Eğer böyle bir şey ifade ediyor mu bu ?
Vorac

1
public class Executor {

private Executor() {} // helper class

  public void execute(MyStepsRunnable r) {
    r.step1();
    r.step2();
    r.step3();
  }
}

interface MyStepsRunnable {

  void step1();
  void step2();
  void step3();
}

Bu kalıbı kullanarak, herhangi bir uygulayıcının bu sıraya göre uygulanacağından emin olabilirsiniz. Bir adım daha ileri gidebilir ve özel uygulama yollarına sahip Yürütücüler inşa edecek bir Yürütücü Fabrika'yı yapabilirsiniz.


Başka bir yorumda, bunu bir adım oluşturucu uygulaması olarak adlandırdınız, ancak değil. Eğer bir MyStepsRunnable örneğiniz varsa, adım 1'den önce adım 3'ü çağırabilirsiniz. Bir adım oluşturucu uygulaması ideone.com/UDECgY . Fikir sadece bir adım2 ile bir adım2 çalıştırarak almaktır. Böylece yöntemleri doğru sırayla çağırmaya zorlanırsınız. Örneğin, bkz. Stackoverflow.com/q/17256627/1281433 .
Joshua Taylor

Kullanılma şeklini sınırlamak için onu korumalı yöntemlerle (veya hatta varsayılan) bir soyut sınıfa dönüştürebilirsiniz. Uygulayıcıyı kullanmaya zorlanacaksınız, ancak mevcut uygulamada bir veya iki kusur olabileceğini biliyorum.
Silviu Burcea,

Bu hala bir adım oluşturucu yapmaz. Kodunuzda, bir kullanıcının farklı adımlar arasında kod çalıştırmak için yapabileceği hiçbir şey yoktur . Buradaki fikir yalnızca kod sıralamak değildir (genel ya da özel ya da başka şekilde kapsüllenmiş olsun) Kodunuzda görüldüğü gibi, basitçe yapmanız yeterli step1(); step2(); step3();. Adım oluşturucunun amacı, bazı adımları gösteren bir API vermek ve onların arandığı sırayı zorlamaktır. Bir programcının adımlar arasında başka şeyler yapmasını engellememelidir.
Joshua Taylor
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.