it-swarm-eu.dev

Existuje nějaký důvod pro použití tříd „prostých starých dat“?

Ve starém kódu občas vidím třídy, které nejsou ničím jiným než obaly pro data. něco jako:

class Bottle {
   int height;
   int diameter;
   Cap capType;

   getters/setters, maybe a constructor
}

Moje chápání OO je, že třídy jsou struktury pro data a způsoby práce s těmito daty) Zdá se, že to vylučuje objekty tohoto typu. Pro mě nejsou nic více než structs a druh porážky účelu OO. Nemyslím si, že je to nutně zlo, i když to může být vůně kódu.

Existuje případ, kdy by takové předměty byly nutné? Pokud se používá často, činí to návrh podezřelým?

44
Michael K

Rozhodně to není zlé, ani kódový zápach v mé mysli. Datové kontejnery jsou platným občanem OO==================== OO $ === občan).

public void DoStuffWithBottle(Bottle b)
{
    // do something that doesn't modify Bottle, so the method doesn't belong
    // on that class
}

než

public void DoStuffWithBottle(int bottleHeight, int bottleDiameter, Cap capType)
{
}

Použití třídy také umožňuje přidat do parametru Bottle další parametr bez úpravy každého volajícího DoStuffWithBottle. A můžete podtřídu Bottle a v případě potřeby dále zvýšit čitelnost a organizaci vašeho kódu.

Existují také prosté datové objekty, které lze vrátit například v důsledku dotazu na databázi. Věřím, že termín pro ně v tomto případě je „Objekt přenosu dat“.

V některých jazycích existují i ​​další úvahy. Například v C # se třídy a struktury chovají odlišně, protože struktury jsou typ hodnoty a třídy jsou referenční typy.

68
Adam Lear

V některých případech jsou datové třídy platné. DTO jsou jedním z dobrých příkladů, o nichž se zmínila Anna Lear. Obecně byste je však měli považovat za semínko třídy, jejíž metody dosud nepučily. A pokud se s nimi setkáte ve starém kódu, zacházejte s nimi jako se silnou vůní kódu. Často je používají starší programátoři C/C++, kteří nikdy neprováděli přechod na OO programování a jsou znakem procedurálního programování.) Spoléhají se vždy na getry a settery (nebo ještě horší na přímý přístup ne soukromých členů) vás může dostat do potíží dříve, než to budete vědět. Zvažte příklad externí metody, která potřebuje informace z Bottle.

Zde Bottle je třída dat):

void selectShippingContainer(Bottle bottle) {
    if (bottle.getDiameter() > MAX_DIMENSION || bottle.getHeight() > MAX_DIMENSION ||
            bottle.getCapType() == Cap.FANCY_CAP ) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

Zde jsme dali Bottle nějaké chování):

void selectShippingContainer(Bottle bottle) {
    if (bottle.isBiggerThan(MAX_DIMENSION) || bottle.isFragile()) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

První metoda porušuje zásadu Tell-Don't-Ask a ponecháním Bottle hloupého jsme nechali implicitní znalosti o lahvích, jako je to, co dělá jednoho křehkého (Cap), vklouznout do logika, která je mimo třídu Bottle. Musíte být na nohou, abyste zabránili tomuto druhu „úniku“, když se obvykle spoléháte na getry.

Druhá metoda se ptá Bottle pouze na to, co potřebuje k provedení své práce, a ponechává Bottle na rozhodnutí, zda je křehká nebo větší než daná velikost. Výsledkem je mnohem volnější propojení mezi metodou a implementací Bottle. Příjemným vedlejším účinkem je, že metoda je čistší a výraznější.

Zřídka využijete mnoho těchto polí objektu, aniž byste psali nějakou logiku, která by měla zůstat ve třídě s těmito poli. Zjistěte, co je to za logiku a potom ji přesuňte tam, kam patří.

25
Mike E

Pokud je to věc, kterou potřebujete, je to v pořádku, ale prosím, udělejte to jako

public class Bottle {
    public int height;
    public int diameter;
    public Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }
}

místo něčeho podobného

public class Bottle {
    private int height;
    private int diameter;
    private Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getDiameter() {
        return diameter;
    }

    public void setDiameter(int diameter) {
        this.diameter = diameter;
    }

    public Cap getCapType() {
        return capType;
    }

    public void setCapType(Cap capType) {
        this.capType = capType;
    }
}

Prosím.

7
compman

Jak řekla @ Anna, rozhodně to není zlé. Určitě můžete operace (metody) zařazovat do tříd, ale pouze pokud chcete to. Nemáte mít.

Dovolte mi trochu sejmout myšlenku, že musíte do tříd zařadit operace, spolu s myšlenkou, že třídy jsou abstrakce. V praxi to programátory podporuje

  1. Vytvořte více tříd, než je třeba (redundantní struktura dat). Pokud datová struktura obsahuje více složek, než je nezbytně nutné, je normalizována, a proto obsahuje nekonzistentní stavy. Jinými slovy, pokud je změněna, musí být změněna na více než jednom místě, aby zůstala konzistentní. Pokud nebudou provedeny všechny koordinované změny, bude to nekonzistentní a bude to chyba.

  2. Vyřešte problém 1 zadáním metod notifikace, takže pokud se změní část A, pokusí se šířit nezbytné změny do částí B a C. To je hlavní důvod, proč se doporučuje mít get-and -set metody přístupu. Protože se jedná o doporučený postup, zdá se, že ospravedlňuje problém 1, což způsobuje více problému 1 a více řešení 2. To má za následek nejen chyby způsobené neúplnou implementací oznámení, ale také problém sappingového výkonu u oznámení o útěku. Nejedná se o nekonečné výpočty, pouze o velmi dlouhé výpočty.

Tyto koncepty se učí jako dobré věci, obvykle učitelé, kteří nemuseli pracovat v rámci miliónových aplikací pro monstrum, které se těmito otázkami zabývají.

Zde je to, co se snažím udělat:

  1. Udržujte data co možná normalizovaná, aby při změně dat byla provedena co nejméně kódových bodů, aby se minimalizovala pravděpodobnost vstupu do nekonzistentního stavu.

  2. Pokud musí být data nestandardizována a je nevyhnutelná redundance, nepoužívejte oznámení ve snaze udržet je konzistentní. Spíše tolerujte dočasnou nekonzistenci. Vyřešte nekonzistenci s periodickými zametáními daty pomocí procesu, který to dělá jen to. Tím se centralizuje odpovědnost za udržování konzistence, přičemž se zabrání problémům s výkonem a správností, kterým jsou oznámení náchylná. Výsledkem je kód, který je mnohem menší, bez chyb a efektivní.

6
Mike Dunlavey

U herního designu může režie 1000 volání funkcí a posluchačů událostí někdy stát za to mít třídy, které ukládají pouze data, a mít jiné třídy, které procházejí všemi třídami dat pouze pro provádění logiky.

3
AttackingHobo

Souhlasím s Annou Learovou,

Rozhodně to není zlé, ani kódový zápach v mé mysli. Datové kontejnery jsou platným občanem OO====================== OO $ === občan).

Někdy lidé zapomněli přečíst 1999 Java) konvence kódování, díky nimž je zcela zřejmé, že tento druh programování je naprosto v pořádku. Ve skutečnosti, pokud se tomu vyhnete, bude váš kód vonět! (Příliš mnoho getterů)/setters)

Od Java Konvence kódu 1999: ) Příkladem vhodných proměnných veřejné instance je případ, kdy je třída v podstatě datovou strukturou, bez chování. , pokud byste místo třídy použili strukturu (pokud Java podporovaná struktura), je vhodné zveřejnit proměnné instance třídy. - http://www.Oracle.com/technetwork/Java/javase/documentation/codeconventions-137265.html#177

Při správném použití jsou POD (jednoduché staré datové struktury) lepší než POJO, stejně jako POJO jsou často lepší než EJB.
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures

3
Richard Faber

Tento druh tříd je docela užitečný, když se zabýváte středně velkými/velkými aplikacemi, z několika důvodů:

  • je docela snadné vytvořit několik testovacích případů a zajistit, aby byla data konzistentní.
  • drží všechny druhy chování, které se týkají těchto informací, takže se snižuje doba sledování datových chyb
  • Jejich použití by mělo vést k tomu, aby byly argy lehké.
  • Při použití ORM poskytuje tato třída flexibilitu a konzistenci. Přidáním složitého atributu, který se počítá na základě jednoduchých informací již ve třídě, se přepíše jedna jednoduchá metoda. To je mnohem agilnější a produktivnější, než když budete muset zkontrolovat svou databázi a zajistit, aby všechny databáze byly opraveny novou úpravou.

Abych to shrnul, podle mých zkušeností jsou obvykle užitečnější než otravné.

3
guiman

Struktury mají své místo, dokonce i v Javě. Měli byste je používat pouze v případě, že jsou splněny následující dvě věci:

  • Musíte pouze agregovat data, která nemají žádné chování, např. předat jako parametr
  • Nezáleží na tom, jaké hodnoty mají agregovaná data

Pokud je tomu tak, měli byste pole zveřejnit a přeskočit getters/setters. Getters a setters jsou stejně neohrabaný a Java je hloupé za to, že nemají vlastnosti jako užitečný jazyk. Protože váš strukturní objekt by stejně neměl mít žádné metody, veřejné pole dávají největší smysl.

Pokud se však některý z nich nevztahuje, jednáte se skutečnou třídou. To znamená, že všechna pole by měla být soukromá. (Pokud absolutně potřebujete pole v dostupnějším rozsahu, použijte getter/setter.)

Chcete-li zkontrolovat, zda má vaše předpokládaná struktura chování, podívejte se, kdy jsou pole použita. Pokud se zdá, že to porušuje řekněte, neptejte se , musíte toto chování přesunout do vaší třídy.

Pokud by se některá vaše data neměla změnit, musíte všechna tato pole učinit konečnými. Můžete zvážit vytvoření vaší třídy neměnné . Pokud potřebujete ověřit svá data, zadejte ověření v stavitelích a konstruktorech. (Užitečným trikem je definování soukromého nastavovače a úprava pole v rámci vaší třídy pouze pomocí tohoto nastavovače.)

Příklad vaší láhve by s největší pravděpodobností neuspěl v obou testech. Můžete mít (vymyšlený) kód, který vypadá takto:

public double calculateVolumeAsCylinder(Bottle bottle) {
    return bottle.height * (bottle.diameter / 2.0) * Math.PI);
}

Místo toho by to mělo být

double volume = bottle.calculateVolumeAsCylinder();

Pokud byste změnili výšku a průměr, byla by to stejná láhev? Asi ne. Ty by měly být konečné. Je záporná hodnota ok pro průměr? Musí být vaše láhev vyšší, než je široká? Může být čepice nulová? Ne? Jak to potvrzujete? Předpokládejme, že klient je hloupý nebo zlý. ( Nelze poznat rozdíl. ) Musíte zkontrolovat tyto hodnoty.

Vaše nová třída lahví by mohla vypadat takto:

public class Bottle {

    private final int height, diameter;

    private Cap capType;

    public Bottle(final int height, final int diameter, final Cap capType) {
        if (diameter < 1) throw new IllegalArgumentException("diameter must be positive");
        if (height < diameter) throw new IllegalArgumentException("bottle must be taller than its diameter");

        setCapType(capType);
        this.height = height;
        this.diameter = diameter;
    }

    public double getVolumeAsCylinder() {
        return height * (diameter / 2.0) * Math.PI;
    }

    public void setCapType(final Cap capType) {
        if (capType == null) throw new NullPointerException("capType cannot be null");
        this.capType = capType;
    }

    // potentially more methods...

}
3
Eva

IMHO v silně objektově orientovaných systémech často neexistuje dost tříd. Musím to pečlivě kvalifikovat.

Samozřejmě, pokud mají datová pole široký rozsah a viditelnost, může to být velmi nežádoucí, pokud ve vaší kódové základně manipulují s těmito daty stovky nebo tisíce míst. To vyžaduje potíže a potíže s udržováním invariantů. Zároveň to však neznamená, že každá třída v celé kódové základně těží z skrytí informací.

Existuje však mnoho případů, kdy taková datová pole budou mít velmi úzký rozsah. Velmi přímým příkladem je soukromá Node třída datové struktury. Často to může kód značně zjednodušit snížením počtu probíhajících interakcí s objektem, pokud by uvedený Node mohl jednoduše sestávat ze surových dat. To slouží jako mechanismus oddělení, protože alternativní verze může vyžadovat obousměrné spojení od, řekněme, Tree->Node a Node->Tree na rozdíl od jednoduše Tree->Node Data.

Složitějším příkladem by byly systémy entitních komponent, jak se často používají v herních strojích. V těchto případech jsou komponenty často pouze prvotními daty a třídami, jako jsou ty, které jste zobrazili. Jejich rozsah/viditelnost však bývá omezená, protože obvykle existuje pouze jeden nebo dva systémy, které by mohly přistupovat k danému typu komponenty. Výsledkem je, že je stále velmi snadné udržovat invarianty v těchto systémech a navíc tyto systémy mají jen velmi málo object->object interakce, takže je velmi snadné pochopit, co se děje na úrovni očí ptáků.

V takových případech můžete skončit s něčím podobnějším, pokud jde o interakce (tento diagram ukazuje interakce, nikoli spojování, protože spojovací diagram může zahrnovat abstraktní rozhraní pro druhý obrázek níže):

enter image description here

... na rozdíl od tohoto:

enter image description here

... a bývalý typ systému bývá mnohem snazší udržovat a zdůvodňovat z hlediska správnosti navzdory skutečnosti, že závislosti skutečně tečou k datům. Získáte mnohem méně spojení především proto, že mnoho věcí lze převést na surová data místo toho, aby objekty vzájemně reagovaly a vytvářely velmi složitý graf interakcí.

0
user204677