it-swarm-eu.dev

Je jeden konfigurační objekt špatný nápad?

Ve většině mých aplikací mám singleton nebo statický "config" objekt, který je zodpovědný za čtení různých nastavení z disku. Téměř všechny třídy jej používají pro různé účely. V podstatě se jedná pouze o hashovou tabulku párů jméno/hodnota. Je to jen pro čtení, takže jsem se příliš nezajímal o to, že mám tolik globálního státu. Ale teď, když začínám s testováním jednotek, začíná to být problém.

Jedním z problémů je, že obvykle nechcete testovat se stejnou konfigurací, jakou používáte. Existuje několik řešení:

  • Dejte konfiguračnímu objektu nastavovač, který se používá POUZE pro testování, takže můžete předávat různá nastavení.
  • Pokračujte v používání jediného konfiguračního objektu, ale změňte jej z singletonu na instanci, kterou předáváte všude, kde je to potřeba. Poté ji můžete sestavit jednou v aplikaci a jednou v testech s různým nastavením.

Ale v každém případě vám zbývá druhý problém: konfigurační objekt může použít téměř každá třída. Takže v testu musíte nastavit konfiguraci pro testovanou třídu, ale také VŠECHNY její závislosti. To může způsobit, že váš testovací kód bude ošklivý.

Začínám dospět k závěru, že tento druh konfiguračního objektu je špatný nápad. Co myslíš? Jaké jsou některé alternativy? A jak začít refactoring aplikace, která používá konfiguraci všude?

45
JW01

Nemám problém s jediným konfiguračním objektem a vidím výhodu v udržování všech nastavení na jednom místě.

Použití tohoto jediného objektu všude však povede k vysoké úrovni propojení mezi konfiguračním objektem a třídami, které jej používají. Pokud potřebujete změnit třídu config, možná budete muset navštívit každou instanci, ve které se používá, a zkontrolovat, zda jste nic neporušili.

Jedním ze způsobů, jak se s tím vypořádat, je vytvořit více rozhraní, která odhalí části konfiguračního objektu, které jsou zapotřebí v různých částech aplikace. Místo povolení jiných tříd přístupu ke konfiguračnímu objektu předáváte instance rozhraní třídám, které jej potřebují. Části aplikace, které používají konfiguraci, tedy závisí spíše na menší sbírce polí v rozhraní než na celé třídě konfigurace. Nakonec můžete pro testování jednotek vytvořit vlastní třídu, která implementuje rozhraní.

Chcete-li tyto myšlenky dále prozkoumat, doporučuji přečíst o SOLIDNÍ principy , zejména o Princip segregace rozhraní a Princip závislosti na závislostech .

39
Kramii

Odděluji skupiny souvisejících nastavení pomocí rozhraní.

Něco jako:

public interface INotificationEmailSettings {
   public string To { get; set; }
}

public interface IMediaFileSettings {
    public string BasePath { get; set; }
}

atd.

Nyní, v reálném prostředí, bude jediná třída implementovat mnoho z těchto rozhraní. Tato třída se může vytáhnout z databáze DB nebo z aplikace nebo co máte, ale často ví, jak získat většinu nastavení.

Segregací podle rozhraní jsou však skutečné závislosti mnohem jasnější a jemnější, což je zjevné zlepšení testovatelnosti.

Ale .. Vždycky nechci být nucen aplikovat nebo poskytovat nastavení jako závislost. Existuje argument, který by mohl být učiněn pro to, abych měl důslednost nebo jednoznačnost. Ve skutečném životě se to však zdá zbytečné omezení.

Takže budu používat statickou třídu jako fasádu, poskytuji snadný vstup odkudkoli do nastavení a v této statické třídě budu služba vyhledávat implementaci rozhraní a získám nastavení.

Vím, že umístění služby získává rychlý palec od většiny, ale přiznejme si to, že závislost prostřednictvím konstruktora je váha, a někdy je tato váha více, než je mi jedno. Service Location je řešením pro udržení testovatelnosti prostřednictvím programování rozhraní a umožnění více implementací a zároveň poskytuje pohodlí (v pečlivě měřených a přiměřeně málo situacích) statického vstupního bodu singleton.

public static class AllSettings {
    public INotificationEmailSettings NotificationEmailSettings {
        get {
            return ServiceLocator.Get<INotificationEmailSettings>();
        }
    }
}

Považuji tento mix za nejlepší ze všech světů.

8
quentin-starin

Ano, jak jste si uvědomili, globální konfigurační objekt ztěžuje testování jednotek. Mít „tajný“ nastavovač pro testování jednotek je rychlý hack, který, i když ne pěkný, může být velmi užitečný: umožňuje vám začít psát testy jednotek, abyste mohli svůj kód v průběhu času změnit na lepší design.

(Povinný odkaz: Efektivně pracuje se starým kódem . Obsahuje tento a mnoho dalších neocenitelných triků pro testování staršího kódu jednotky.)

Podle mých zkušeností je nejlepší mít co nejméně závislostí na globální konfiguraci. Což není nutně nula - vše záleží na okolnostech. S několika vysoce organizovanými třídami organizátorů, které přistupují ke globální konfiguraci a předávají skutečné konfigurační vlastnosti objektům, které vytvářejí a používají, může fungovat dobře, pokud to organizátoři dělají pouze takto, např. samy o sobě neobsahují mnoho testovatelných kódů. To vám umožní otestovat většinu nebo všechny důležité funkce aplikace, které lze otestovat, a přitom stále neporušovat existující schéma fyzické konfigurace.

To se však již blíží originálnímu řešení závislosti, jako je Spring for Java. Pokud můžete přejít na takový rámec, dobře. Ale v reálném životě, a zejména při jednání se starší aplikací, je často pomalým a pečlivým refaktoringem k DI nejlepší dosažitelný kompromis.

4
Péter Török

Podle mých zkušeností je ve světě Java) to, pro co je jaro určeno. Vytváří a spravuje objekty (fazole) na základě určitých vlastností, které jsou nastaveny za běhu, a je jinak pro vaši společnost transparentní Můžete jít se souborem test.config, o kterém se zmiňuje Anon., nebo můžete do konfiguračního souboru zahrnout nějakou logiku, kterou jaro zpracovává, aby nastavil vlastnosti na základě jiného klíče (např. název hostitele nebo prostředí).

Pokud jde o váš druhý problém, můžete se obejít nějakou rearchitekturou, ale není to tak těžké, jak se zdá. To ve vašem případě znamená, že byste například neměli globální objekt Config, který používají různé jiné třídy. Tyto další třídy byste nakonfigurovali pouze na jaře a pak nemáte žádný konfigurační objekt, protože vše, co potřebovala konfigurace, se dostalo skrze jaro, takže tyto třídy můžete použít přímo ve svém kódu.

2
Justin Beal

Tato otázka se ve skutečnosti týká obecnějšího problému než konfigurace. Jakmile jsem četl slovo „singleton“, okamžitě jsem přemýšlel o všech problémech spojených s tímto vzorcem, v neposlední řadě o špatné testovatelnosti.

Vzor Singleton je „považován za škodlivý“. To znamená, že to není vždy špatná věc, ale obvykle je. Pokud jste někdy uvažovali o použití singletonového vzoru pro cokoli, přestaňte uvažovat:

  • Budete ji někdy muset podtřídit?
  • Budete někdy potřebovat programovat rozhraní?
  • Potřebujete proti tomu spustit testy jednotek?
  • Budete jej muset často upravovat?
  • Je vaše aplikace určena k podpoře více produkčních platforem/prostředí?
  • Znepokojuje vás využití paměti?

Pokud vaše odpověď zní „ano“ na některou z těchto věcí (a pravděpodobně na několik dalších věcí, na které jsem nepřemýšlel), pravděpodobně nebudete chtít použít singleton. Konfigurace často vyžadují mnohem větší flexibilitu, než může poskytnout singleton (nebo v tomto ohledu jakákoli instanceless třída).

Pokud chcete téměř všechny výhody singleton bez bolesti, pak použijte závislost injekce rámec, jako je jaro nebo hrad, nebo co je k dispozici pro vaše prostředí. Tímto způsobem ji budete muset kdykoli prohlásit pouze jednou a kontejner automaticky poskytne instanci, co bude potřeba.

2
Aaronaught

Jedním ze způsobů, jak jsem to zvládl v C #, je mít singletonový objekt, který se zamkne během vytváření instance a inicializuje všechna data najednou pro použití všemi klientskými objekty. Pokud tento singleton dokáže zpracovat páry klíčů a hodnot, můžete uložit jakýkoli způsob dat, včetně složitých klíčů pro použití mnoha různými klienty a typy klientů. Pokud si chcete zachovat dynamiku a podle potřeby načíst nová klientská data, můžete ověřit existenci hlavního klíče klienta a, pokud chybí, načíst data pro tohoto klienta a připojit je do hlavního slovníku. Je také možné, že primární konfigurační singleton může obsahovat sady klientských dat, kde násobky stejného typu klienta používají stejná data, která jsou přístupná prostřednictvím primárního konfiguračního singletonu. Většina této organizace konfiguračních objektů může záviset na tom, jak vaši konfigurační klienti potřebují přístup k těmto informacím a zda jsou tyto informace statické nebo dynamické. Použil jsem jak konfigurace klíč/hodnota, tak i API specifických objektů v závislosti na tom, co bylo potřeba.

Jeden z mých konfiguračních singletů může přijímat zprávy, které se mají znovu načíst z databáze. V tomto případě se načtím do druhé kolekce objektů a uzamknu pouze hlavní kolekci, abych vyměnil kolekce. Tím se zabrání blokování čtení na jiných vláknech.

Pokud načítáte z konfiguračních souborů, můžete mít hierarchii souborů. Nastavení v jednom souboru může určit, které z ostatních souborů se mají načíst. Tento mechanismus jsem použil u služeb C # Windows, které mají více volitelných součástí, z nichž každá má vlastní konfigurační nastavení. Vzory struktury souborů byly ve všech souborech stejné, ale byly načteny či nikoli na základě nastavení primárního souboru.

0
Tim Butterfield

Třída, kterou popisujete, zní jako Boží objekt antipattern s výjimkou dat namísto funkcí. To je problém. Pravděpodobně byste měli nechat načíst a uložit konfigurační data do příslušného objektu a data znovu načíst, pokud je to z nějakého důvodu nutné.

Navíc používáte Singleton z důvodu, který není vhodný. Singletony by se měly používat pouze tehdy, pokud existence více objektů vytvoří špatný stav. Použití Singleton v tomto případě je nevhodné, protože mít více než jednu z konfiguračních čteček by nemělo okamžitě způsobit chybový stav. Pokud máte více než jednu, pravděpodobně to děláte špatně, ale není nezbytně nutné, abyste měli pouze jednu z konfiguračních čteček.

A konečně, vytvoření takového globálního stavu je porušením zapouzdření, protože povolujete více tříd, než je třeba vědět, přímý přístup k datům.

0
indyK1ng

Myslím, že pokud existuje spousta nastavení, s „shluky“ souvisejících, má smysl rozdělit je do samostatných rozhraní s příslušnými implementacemi - pak je tam, kde je to potřeba, aplikovat pomocí DI. Získáte testovatelnost, nižší propojení a SRP.

V této záležitosti mě inspiroval Joshua Flanagan z Los Techies; napsal článek o této záležitosti před časem.

0
Grant Palin