Kdy podtřídy namísto rozlišování chování

hlasů
9

Mám potíže rozhodování o tom, kdy bych měl být subclassing místo pouhým přidáním proměnnou instance, který představuje různé způsoby třídě a pak nechat metody třídy čin dle zvoleného režimu.

Řekněme například, že jsem si základna auto třídu. V mém programu budu zabývat třemi různými typy vozů. Závodní automobily , autobusy a rodinné modely . Každý bude mít svou vlastní implementaci ozubených kol, jak se otočit a nastavení sedadla. Měl jsem podtřídy moje auto do tří různých modelů, nebo mám vytvořit typ proměnné a provést převody, soustružení a sedací generické takže se bude jednat lišit v závislosti na typu vozu byl vybrán?

V mé současné situaci jsem pracoval na hře, a já jsem si uvědomil, že to začíná být trochu chaotický, tak se ptám na radu možná refaktoringu mého současného kódu. V zásadě existují různé mapy, a každá mapa může být jeden ze tří režimů. V závislosti na zvoleném režimu se mapa definována jako tam bude odlišné chování a mapa bude postavena jiným způsobem. V jednom režimu budu muset rozdat nájmy hráčům a plodit tvory na limitu základu, přičemž jedno je hráč responsable pro tření stvoření a ještě v jiném tam mohl být nějaký automatizovaný plodil bytosti podél s ty a hráči stavbu budov hráče třel , Takže jsem přemýšlel, zda by bylo nejlepší mít třídu podkladové mapy, a pak podtřídy do každého z různých druhů, nebo zda bude pokračovat po mé aktuální cestu přidávání diferencovaného chování v závislosti na tom, co je typ mapa proměnná je nastavena na hodnotu ,

Položena 27/08/2009 v 02:18
zdroj uživatelem
V jiných jazycích...                            


5 odpovědí

hlasů
7

Všechny úvěry AtmaWeapon z http://www.xtremevbtalk.com záznamníky v tomto vlákně

Jádrem obou případech je to, co cítím, je základním pravidlem objektově orientovaného designu: Jednotný odpovědnost princip. Dva způsoby, jak vyjádřit to jsou:

"A class should have one, and only one, reason to change."
"A class should have one, and only one, responsibility."

SRP je ideál, který nemůže být vždy splněna, a podle této zásady je těžké . Mám ve zvyku střílet „Třída by měla mít co nejméně odpovědnosti jak je to možné.“ Naše mozky jsou velmi dobré v nás přesvědčuje, že velmi složité jediná třída je méně komplikovaný než několik velmi jednoduchých tříd. Začal jsem dělat to nejlepší zapisovat menší třídy v poslední době, a já jsem zažila výrazné snížení počtu chyb ve svém kódu. To zkusit na několik projektů, než propouštět ji.

Poprvé jsem navrhl, že namísto spuštění design vytvořením mapa základní třídu a tři podřízené třídy, začít s designem, který odděluje jedinečné chování každé mapy do druhé třídy, která reprezentuje generické „mapa chování“. Tento příspěvek se zabývá dokazuje tento přístup je lepší. Je to pro mě těžké být konkrétní, aniž by docela intimní znalost kódu, ale budu používat velmi jednoduchou představu o mapě:

Public Class Map
    Public ReadOnly Property MapType As MapType

    Public Sub Load(mapType)
    Public Sub Start()
End Class

MapType indikuje, která ze tří typů map mapa zastupuje. Pokud chcete změnit typ mapy, zavoláte Load()s typem mapy, kterou chcete použít; to dělá, co je třeba udělat, aby vymazat aktuální mapu stav obnovit pozadí atd Po načtení mapy, Start () se nazývá. V případě, že mapa má veškeré funkce, jako „spawn monstrum x každých y sekund,“ Start () je zodpovědný za konfiguraci těchto chování.

To je to, co máte nyní, a ty jsou moudré, že je to špatný nápad. Vzhledem k tomu, jsem se zmínil SRP, pojďme počítat odpovědnost mapy .

  • Má spravovat informace o stavu pro všechny tři typy map. (3+ povinnosti *)
  • Load() musí pochopit, jak odstranit stav pro všechny tři typy map a jak nastavit výchozí stav pro všechny tři typy map (6 odpovědnosti)
  • Start()Musí vědět, co dělat pro každý typ mapy. (3) Odpovědnost

** Technicky každou proměnnou je odpovědností, ale já jsem to zjednodušil. *

Pro konečnou celkem co se stane, když přidáte čtvrtý typ mapy? Budete muset přidat další stavové proměnné (1+ kompetencí), aktualizace Load(), aby bylo možné vyčistit a inicializaci stavu (2 odpovědnosti) a aktualizace Start()zvládnout nové chování (1) odpovědnost. Tak:

Počet Mapodpovědnosti: 12+

Počet změn nutných pro nové mapy: 4+

K dispozici je také další problémy. Kurzy jsou, několik typů map budou mít podobnou informací o stavu, takže budete sdílet proměnné mezi státy. Díky tomu je mnohem pravděpodobnější, že Load()bude zapomenete nastavit nebo zrušte proměnnou, protože si nemusí uvědomit, že jedna mapa používá _foo pro jeden účel a další používá ho k jinému účelu úplně.

Není to snadné, aby tento test, a to buď. Předpokládejme, že chcete napsat test pro scénář „Když jsem vytvořit mapu‚Spawn Monsters‘, mapa by měla plodit jeden nový monstrum každých pět sekund.“ Je snadné diskutovat o tom, jak byste mohli vyzkoušet toto: vytvořit mapy, nastavit jeho typ, spustit, počkat trochu déle než pět sekund a zkontrolujte počet nepřítele. Nicméně, naše rozhraní je momentálně bez „count nepřítel“ vlastnictví. Mohli bychom ho přidat, ale co když je to jen mapa, která má počítat nepřítel? Když k tomu přidáme vlastnost, budeme mít vlastnost, která je neplatná v 2/3 případů. Je to také není zcela jasné, že jsme testování „podhoubí“ monstra mapy bez čtení kódu při zkoušce, jelikož všechny testy budou testovat Maptřídu.

Dalo by se jistě Mapabstraktní základní třídy, Start()MustOverride, a odvodit jeden nový typ pro každý typ mapy. Nyní je odpovědností Load()je někde jinde, protože objekt nemůže nahradit sama s různými stupni. Můžete také provést tovární třídu pro toto:

Class MapCreator
    Public Function GetMap(mapType) As Map
End Class

Nyní náš Map hierarchie by mohla vypadat takto (pouze jeden odvozený mapa byla definována pro jednoduchost):

Public MustInherit Class Map
    Public MustOverride Sub Start()
End Class

Public Class RentalMap
    Inherits Map

    Public Overrides Sub Start()
End Class

Load()už není potřeba z důvodů již výše. MapTypeJe zbytečné na mapě, protože můžete zjistit typ objektu, aby viděli, co to je (pokud máte k dispozici několik typů RentalMap, pak se to stane znovu použitelné.) Start()je přepsána v každé odvozené třídě, takže jste se přestěhovali odpovědnost státu vedení jednotlivých tříd. Pojďme udělat další SRP kontrolu:

Mapa base class 0 odpovědnost

Mapa odvozené třídy - musí řídit stav (1) - musí vykonávat nějakou typově specifické práce (1)

Celkem: 2 Odpovědnost

Přidání nové mapy (stejně jako výše) 2 odpovědnosti

Celkový počet zodpovědnosti za své třídě: 2

Náklady na přidání nového mapu třída: 2

To je mnohem lepší. Co naše testovací scénáře? Jsme v lepší kondici, ale ještě není úplně v pořádku. Můžeme dostat pryč s uvedení „počet nepřátel“ majetku na našem odvozené třídy, protože každá třída je samostatná a můžeme obsazení na různé typy map, pokud potřebujeme konkrétní informace. Přesto, co když máte RentalMapSlowa RentalMapFast? Budete muset duplikovat testy pro každou z těchto tříd, protože každá z nich má jinou logiku. Takže pokud máte 4 testů a 12 různých map, budete psát a mírně ladění 48 testů. Jak můžeme tento problém vyřešit?

To, co jsme udělali, když jsme učinili odvozené třídy? Identifikovali jsme část třídy, která měnila pokaždé a tlačil ji do podtříd. Co když místo podtřídy jsme vytvořili samostatnou MapBehaviortřídu, která můžeme vyměnit dovnitř a ven podle libosti? Podívejme se, co by to mohlo vypadat s jedním odvozeným chování:

Public Class Map
    Public ReadOnly Property Behavior As MapBehavior

    Public Sub SetBehavior(behavior)
    Public Sub Start()
End Class

Public MustInherit Class MapBehavior
    Public MustOverride Sub Start()
End Class

Public Class PlayerSpawnBehavior
    Public Property EnemiesPerSpawn As Integer
    Public Property MaximumNumberOfEnemies As Integer
    Public ReadOnly Property NumberOfEnemies As Integer

    Public Sub SpawnEnemy()
    Public Sub Start()
End Class

Nyní pomocí mapy zahrnuje dávat to zvláštní MapBehaviora volání Start(), které přenáší na chování je Start(). Veškeré informace stav je v objektu chování, aby se mapa skutečně nemusí vědět nic o tom. Přesto, co když chcete určitý typ mapy, zdá nepohodlné muset vytvořit chování pak vytvořit mapu, že jo? Takže si odvodit některé třídy:

Public Class PlayerSpawnMap
    Public Sub New()
        MyBase.New(New PlayerSpawnBehavior())
    End Sub
End Class

To je ono, jeden řádek kódu pro novou třídu. Chcete hráč potěr mapu pevný?

Public Class HardPlayerSpawnMap
    Public Sub New()
        ' Base constructor must be first line so call a function that creates the behavior
        MyBase.New(CreateBehavior()) 
    End Sub

    Private Function CreateBehavior() As MapBehavior
        Dim myBehavior As New PlayerSpawnBehavior()
        myBehavior.EnemiesPerSpawn = 10
        myBehavior.MaximumNumberOfEnemies = 300
    End Function
End Class

Tak, jak je to odlišné od mající vlastnosti na odvozené třídy? Z hlediska chování, že to není příliš neliší. Z testování hlediska se jedná o zásadní průlom. PlayerSpawnBehaviormá svou vlastní sadu testů. Ale od té doby HardPlayerSpawnMap, a PlayerSpawnMapoba používají PlayerSpawnBehavior, pak v případě, Testoval jsem PlayerSpawnBehaviornemám psát nějaké testy chování souvisejících pro mapu, která používá chování! Srovnejme testovací scénáře.

V „jedné třídě s parametrem typu“ případ, pokud tam jsou 3 úrovně obtížnosti pro 3 chování, a každý chování má 10 zkoušek, budete psát o 90 testů (bez testů, zda jde z každého jednání na další díla .) V „odvozených tříd“ scénář, budete mít 9 tříd, které potřebují 10 testů každý: 90 testů. Ve scénáři „class chování“, napíšete 10 testů pro každou chování: 30 testů.

Zde je zodpovědnost shodují: Mapa má 1 povinnost: sledovat v chování. Chování má 2 úkoly: udržení stavu a provádět akce.

Celkový počet zodpovědnosti za své třídě: 3

Náklady na přidání nového mapu třídy: 0 (opakovaně chování) nebo 2 (nové chování)

Takže můj názor je, že „třída chování“ scénář není těžší psát než „odvozených tříd“ scénář, ale může výrazně snížit zátěž testování. Četl jsem o technikách, jako je tento, a propustil jako „příliš mnoho problémů“ po mnoho let a teprve nedávno uvědomil svou hodnotu. To je důvod, proč jsem napsal téměř 10.000 znaků na to vysvětlit a zdůvodnit.

Odpovězeno 31/08/2009 v 03:04
zdroj uživatelem

hlasů
3

Měli byste podtřídy, kde se váš typ dítě je nějaká specializace základního typu. Jinými slovy, měli byste se vyhnout dědictví stačí, pokud funkčnost. Vzhledem k tomu, Střídání Princip Liskov se uvádí: „Pokud je s podtypem T, pak objekty typu T v programu mohou být nahrazeny objekty typu S aniž by se změnila některá z žádoucích vlastností tohoto programu“

Odpovězeno 27/08/2009 v 02:37
zdroj uživatelem

hlasů
1

Nemám program v jazyce C #, ale v Ruby on Rails, Xcode a Mootools (javascript OOP rámec) tutéž otázku by mohl být požádán.

Nemám rád, když metodu, která nebude nikdy použit, pokud určitý, stálý, majetek je špatný. Stejně jako v případě, že je to VW Bug, nebude nikdy obrátil některé převody. To je hloupost.

Mám-li najít nějaké metody, jako je, že se snažím abstraktní všechno ven, které mohou být sdíleny mezi všemi mými různými „auta“ do nadřazené třídy, metody a vlastnosti, které mají být použity při každém druhu auta, a pak definovat sub třídy s jejich specifické metody.

Odpovězeno 27/08/2009 v 03:28
zdroj uživatelem

hlasů
1

Ve vašem případě bych jít s hybridním přístupem (což by se dalo nazvat složení, já nevím), kde je váš způsob mapa proměnná je vlastně samostatný objekt, který ukládá všechny související data / chování režimu mapy. Tímto způsobem můžete mít tolik způsobů, jak se vám líbí, aniž by ve skutečnosti dělat příliš mnoho na třídu mapy.

gutofb7 přibil ji na hlavu, aby se, pokud chcete podtřídy něco. Dát více konkrétní příklad: Ve své třídě automobilů, bylo by to jedno, kdekoli v programu, jaký typ auta jste se s tím vyrovnat? Nyní, pokud jste podtřídy Mapa, kolik kód museli byste napsat, že se zabývá konkrétními podtřídy?

Odpovězeno 27/08/2009 v 02:41
zdroj uživatelem

hlasů
1

V konkrétním problému, se kterým mluvili s mapami a tření, myslím, že to je případ, kdy chcete upřednostňují složení přes dědičnost . Když si myslíte o tom, že nejsou přesně tři různé typy mapy. Místo toho, oni jsou stejné mapa se třemi různými strategiemi pro tření. Takže pokud je to možné, měli byste se funkce tření samostatnou třídu a mít instance třídy reprodukující jako člen vaší mapy. Pokud jsou všechny ostatní rozdíly v „režimů“ pro vaše mapy jsou v zásadě podobné, nemusí mít podtřídy mapy vůbec, ačkoli subclassing různých složek (tj mají spawn_strategy základní třídy a podtřídy tři typy reprodukující se z toho) , nebo alespoň dát jim společné rozhraní, bude pravděpodobně nutné.

Vzhledem k tomu, váš komentář, že každý typ mapy má být koncepčně odlišné, pak bych navrhnout subclassing, as, která se zdá splnit princip substituce Liskov je. To však neznamená, že byste se měli vzdát složení úplně. U těch vlastností, které každý typ mapy má, ale mohou mít různé chování / implementaci, měli byste zvážit svoji základní třídu nechat jako komponenty. Tímto způsobem můžete stále míchat a funkčnost shody, pokud je to nutné, při použití dědičnosti pro udržení oddělení znepokojení.

Odpovězeno 27/08/2009 v 02:29
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more