如何寫一手漂亮的模型:面向?qū)ο缶幊痰脑O(shè)計(jì)原則綜述
面向?qū)ο蟮木幊淘趯?shí)現(xiàn)想法乃至系統(tǒng)的過程中都非常重要,我們不論是使用 TensorFlow 還是 PyTorch 來構(gòu)建模型都或多或少需要使用類和方法。而采用類的方法來構(gòu)建模型會(huì)令代碼非常具有可讀性和條理性,本文介紹了算法實(shí)現(xiàn)中使用類和方法來構(gòu)建模型所需要注意的設(shè)計(jì)原則,它們可以讓我們的機(jī)器學(xué)習(xí)代碼更加美麗迷人。
大多數(shù)現(xiàn)代編程語言都支持并且鼓勵(lì)面向?qū)ο缶幊?OOP)。即使我們最近似乎看到了一些偏離,因?yàn)槿藗冮_始使用不太受 OOP 影響的編程語言(例如 Go, Rust, Elixir, Elm, Scala),但是大多數(shù)還是具有面向?qū)ο蟮膶傩?。我們在這里概括出的設(shè)計(jì)原則也適用于非 OOP 編程語言。
為了成功地寫出清晰的、高質(zhì)量的、可維護(hù)并且可擴(kuò)展的代碼,我們需要以 Python 為例了解在過去數(shù)十年里被證明是有效的設(shè)計(jì)原則。
一、對象類型
因?yàn)槲覀円獓@對象來建立代碼,所以區(qū)分它們的不同責(zé)任和變化是有用的。一般來說,面向?qū)ο蟮木幊逃腥N類型的對象。
1. 實(shí)體對象
這類對象通常對應(yīng)著問題空間中的一些現(xiàn)實(shí)實(shí)體。比如我們要建立一個(gè)角色扮演游戲(RPG),那么簡單的 Hero 類就是一個(gè)實(shí)體對象。
- class Hero:
- def __init__(self, health, mana):
- self._health = health
- self._mana = mana
- def attack(self) -> int:
- """
- Returns the attack damage of the Hero
- """
- return 1
- def take_damage(self, damage: int):
- self._health -= damage
- def is_alive(self):
- return self._health > 0
這類對象通常包含關(guān)于它們自身的屬性(例如 health 或 mana),這些屬性根據(jù)具體的規(guī)則都是可修改的。
2. 控制對象(Control Object)
控制對象(有時(shí)候也稱作管理對象)主要負(fù)責(zé)與其它對象的協(xié)調(diào),這是一些管理并調(diào)用其它對象的對象。我們上面的 RPG 案例中有一個(gè)很棒的例子,F(xiàn)ight 類控制兩個(gè)英雄,并讓它們對戰(zhàn)。
- class Fight:
- class FightOver(Exception):
- def __init__(self, winner, *args, **kwargs):
- self.winner = winner
- super(*args, **kwargs)
- def __init__(self, hero_a: Hero, hero_b: Hero):
- self._hero_a = hero_a
- self._hero_b = hero_b
- self.fight_ongoing = True
- self.winner = None
- def fight(self):
- while self.fight_ongoing:
- self._run_round()
- print(f'The fight has ended! Winner is #{self.winner}')
- def _run_round(self):
- try:
- self._run_attack(self._hero_a, self._hero_b)
- self._run_attack(self._hero_b, self._hero_a)
- except self.FightOver as e:
- self._finish_round(e.winner)
- def _run_attack(self, attacker: Hero, victim: Hero):
- damage = attacker.attack()
- victim.take_damage(damage)
- if not victim.is_alive():
- raise self.FightOver(winner=attacker)
- def _finish_round(self, winner: Hero):
- self.winner = winner
- self.fight_ongoing = False
在這種類中,為對戰(zhàn)封裝編程邏輯可以給我們提供多個(gè)好處:其中之一就是動(dòng)作的可擴(kuò)展性。我們可以很容易地將參與戰(zhàn)斗的英雄傳遞給非玩家角色(NPC),這樣它們就能利用相同的 API。我們還可以很容易地繼承這個(gè)類,并復(fù)寫一些功能來滿足新的需要。
3. 邊界對象(Boundary Object)
這些是處在系統(tǒng)邊緣的對象。任何一個(gè)從其它系統(tǒng)獲取輸入或者給其它系統(tǒng)產(chǎn)生輸出的對象都可以被歸類為邊界對象,無論那個(gè)系統(tǒng)是用戶,互聯(lián)網(wǎng)或者是數(shù)據(jù)庫。
- class UserInput:
- def __init__(self, input_parser):
- self.input_parser = input_parser
- def take_command(self):
- """
- Takes the user's input, parses it into a recognizable command and returns it
- """
- command = self._parse_input(self._take_input())
- return command
- def _parse_input(self, input):
- return self.input_parser.parse(input)
- def _take_input(self):
- raise NotImplementedError()
- class UserMouseInput(UserInput):
- pass
- class UserKeyboardInput(UserInput):
- pass
- class UserJoystickInput(UserInput):
- pass
這些邊界對象負(fù)責(zé)向系統(tǒng)內(nèi)部或者外部傳遞信息。例如對要接收的用戶指令,我們需要一個(gè)邊界對象來將鍵盤輸入(比如一個(gè)空格鍵)轉(zhuǎn)換為一個(gè)可識(shí)別的域事件(例如角色的跳躍)。
4. Bonus:值對象(Value Object)
價(jià)值對象代表的是域(domain)中的一個(gè)簡單值。它們無法改變,不恒一。
如果將它們結(jié)合在我們的游戲中,Money 類或者 Damage 類就表示這種對象。上述的對象讓我們?nèi)菀椎貐^(qū)分、尋找和調(diào)試相關(guān)功能,然而僅使用基礎(chǔ)的整形數(shù)組或者整數(shù)卻無法實(shí)現(xiàn)這些功能。
- class Money:
- def __init__(self, gold, silver, copper):
- self.gold = gold
- self.silver = silver
- self.copper = copper
- def __eq__(self, other):
- return self.gold == other.gold and self.silver == other.silver and self.copper == other.copper
- def __gt__(self, other):
- if self.gold == other.gold and self.silver == other.silver:
- return self.copper > other.copper
- if self.gold == other.gold:
- return self.silver > other.silver
- return self.gold > other.gold
- def __add__(self, other):
- return Money(gold=self.gold + other.gold, silver=self.silver + other.silver, copper=self.copper + other.copper)
- def __str__(self):
- return f'Money Object(Gold: {self.gold}; Silver: {self.silver}; Copper: {self.copper})'
- def __repr__(self):
- return self.__str__()
- print(Money(1, 1, 1) == Money(1, 1, 1))
- # => True
- print(Money(1, 1, 1) > Money(1, 2, 1))
- # => False
- print(Money(1, 1, 0) + Money(1, 1, 1))
- # => Money Object(Gold: 2; Silver: 2; Copper: 1)
它們可以歸類為實(shí)體對象的子類別。
二、關(guān)鍵設(shè)計(jì)原則
設(shè)計(jì)原則是軟件設(shè)計(jì)中的規(guī)則,過去這些年里已經(jīng)證明它們是有價(jià)值的。嚴(yán)格地遵循這些原則有助于軟件達(dá)到一流的質(zhì)量。
1. 抽象(Abstraction)
抽象就是將一個(gè)概念在一定的語境中簡化為原始本質(zhì)的一種思想。它允許我們拆解一個(gè)概念來更好的理解它。
上面的游戲案例闡述了抽象,讓我們來看一下 Fight 類是如何構(gòu)建的。我們以盡可能簡單的方式使用它,即在實(shí)例化的過程中給它兩個(gè)英雄作為參數(shù),然后調(diào)用 fight() 方法。不多也不少,就這些。
代碼中的抽象過程應(yīng)該遵循最少意外(POLA)的原則,抽象不應(yīng)該用不必要和不相關(guān)的行為/屬性。換句話說,它應(yīng)該是直觀的。
注意,我們的 Hero#take_damage() 函數(shù)不會(huì)做一些異常的事情,例如在還沒死亡的時(shí)候刪除角色。但是如果他的生命值降到零以下,我們可以期望它來殺死我們的角色。
2. 封裝
封裝可以被認(rèn)為是將某些東西放在一個(gè)類以內(nèi),并限制了它向外部展現(xiàn)的信息。在軟件中,限制對內(nèi)部對象和屬性的訪問有助于保證數(shù)據(jù)的完整性。
將內(nèi)部編程邏輯封裝成黑盒子,我們的類將更容易管理,因?yàn)槲覀冎滥牟糠挚梢员黄渌到y(tǒng)使用,哪些不行。這意味著我們在保留公共部分并且保證不破壞任何東西的同時(shí)能夠重用內(nèi)部邏輯。此外,我們從外部使用封裝功能變得更加簡單,因?yàn)樾枰紤]的事情也更少。
在大多數(shù)編程語言中,封裝都是通過所謂的 Access modifiers(訪問控制修飾符)來完成的(例如 private,protected 等等)。Python 并不是這方面的最佳例子,因?yàn)樗荒茉谶\(yùn)行時(shí)構(gòu)建這種顯式修飾符,但是我們使用約定來解決這個(gè)問題。變量和函數(shù)前面的_前綴就意味著它們是私有的。
舉個(gè)例子,試想將我們的 Fight#_run_attack 方法修改為返回一個(gè)布爾變量,這意味著戰(zhàn)斗結(jié)束而不是發(fā)生了意外。我們將會(huì)知道,我們唯一可能破壞的代碼就是 Fight 類的內(nèi)部,因?yàn)槲覀兪前堰@個(gè)函數(shù)設(shè)置為私有的。
請記住,代碼更多的是被修改而不是重寫。能夠盡可能清晰、較小影響的方式修改代碼對開發(fā)的靈活性很重要。
3. 分解
分解就是把一個(gè)對象分割為多個(gè)更小的獨(dú)立部分,這些獨(dú)立的部分更易于理解、維護(hù)和編程。
試想我們現(xiàn)在希望 Hero 類能結(jié)合更多的 RPG 特征,例如 buffs,資產(chǎn),裝備,角色屬性。
- class Hero:
- def __init__(self, health, mana):
- self._health = health
- self._mana = mana
- self._strength = 0
- self._agility = 0
- self._stamina = 0
- self.level = 0
- self._items = {}
- self._equipment = {}
- self._item_capacity = 30
- self.stamina_buff = None
- self.agility_buff = None
- self.strength_buff = None
- self.buff_duration = -1
- def level_up(self):
- self.level += 1
- self._stamina += 1
- self._agility += 1
- self._strength += 1
- self._health += 5
- def take_buff(self, stamina_increase, strength_increase, agility_increase):
- self.stamina_buff = stamina_increase
- self.agility_buff = agility_increase
- self.strength_buff = strength_increase
- self._stamina += stamina_increase
- self._strength += strength_increase
- self._agility += agility_increase
- self.buff_duration = 10 # rounds
- def pass_round(self):
- if self.buff_duration > 0:
- self.buff_duration -= 1
- if self.buff_duration == 0: # Remove buff
- self._stamina -= self.stamina_buff
- self._strength -= self.strength_buff
- self._agility -= self.agility_buff
- self._health -= self.stamina_buff * 5
- self.buff_duration = -1
- self.stamina_buff = None
- self.agility_buff = None
- self.strength_buff = None
- def attack(self) -> int:
- """
- Returns the attack damage of the Hero
- """
- return 1 + (self._agility * 0.2) + (self._strength * 0.2)
- def take_damage(self, damage: int):
- self._health -= damage
- def is_alive(self):
- return self._health > 0
- def take_item(self, item: Item):
- if self._item_capacity == 0:
- raise Exception('No more free slots')
- self._items[item.id] = item
- self._item_capacity -= 1
- def equip_item(self, item: Item):
- if item.id not in self._items:
- raise Exception('Item is not present in inventory!')
- self._equipment[item.slot] = item
- self._agility += item.agility
- self._stamina += item.stamina
- self._strength += item.strength
- self._health += item.stamina * 5
- # 缺乏分解的案例
我們可能會(huì)說這份代碼已經(jīng)開始變得相當(dāng)混亂了。
例如,我們的耐力分?jǐn)?shù)為 5 個(gè)生命值,如果將來要修改為 6 個(gè)生命值,我們就要在很多地方修改這個(gè)實(shí)現(xiàn)。
解決方案就是將 Hero 對象分解為多個(gè)更小的對象,每個(gè)小對象可承擔(dān)一些功能。下面展示了一個(gè)邏輯比較清晰的架構(gòu):
- from copy import deepcopy
- class AttributeCalculator:
- @staticmethod
- def stamina_to_health(self, stamina):
- return stamina * 6
- @staticmethod
- def agility_to_damage(self, agility):
- return agility * 0.2
- @staticmethod
- def strength_to_damage(self, strength):
- return strength * 0.2
- class HeroInventory:
- class FullInventoryException(Exception):
- pass
- def __init__(self, capacity):
- self._equipment = {}
- self._item_capacity = capacity
- def store_item(self, item: Item):
- if self._item_capacity < 0:
- raise self.FullInventoryException()
- self._equipment[item.id] = item
- self._item_capacity -= 1
- def has_item(self, item):
- return item.id in self._equipment
- class HeroAttributes:
- def __init__(self, health, mana):
- self.health = health
- self.mana = mana
- self.stamina = 0
- self.strength = 0
- self.agility = 0
- self.damage = 1
- def increase(self, stamina=0, agility=0, strength=0):
- self.stamina += stamina
- self.health += AttributeCalculator.stamina_to_health(stamina)
- self.damage += AttributeCalculator.strength_to_damage(strength) + AttributeCalculator.agility_to_damage(agility)
- self.agility += agility
- self.strength += strength
- def decrease(self, stamina=0, agility=0, strength=0):
- self.stamina -= stamina
- self.health -= AttributeCalculator.stamina_to_health(stamina)
- self.damage -= AttributeCalculator.strength_to_damage(strength) + AttributeCalculator.agility_to_damage(agility)
- self.agility -= agility
- self.strength -= strength
- class HeroEquipment:
- def __init__(self, hero_attributes: HeroAttributes):
- self.hero_attributes = hero_attributes
- self._equipment = {}
- def equip_item(self, item):
- self._equipment[item.slot] = item
- self.hero_attributes.increase(stamina=item.stamina, strength=item.strength, agility=item.agility)
- class HeroBuff:
- class Expired(Exception):
- pass
- def __init__(self, stamina, strength, agility, round_duration):
- self.attributes = None
- self.stamina = stamina
- self.strength = strength
- self.agility = agility
- self.duration = round_duration
- def with_attributes(self, hero_attributes: HeroAttributes):
- buff = deepcopy(self)
- buff.attributes = hero_attributes
- return buff
- def apply(self):
- if self.attributes is None:
- raise Exception()
- self.attributes.increase(stamina=self.stamina, strength=self.strength, agility=self.agility)
- def deapply(self):
- self.attributes.decrease(stamina=self.stamina, strength=self.strength, agility=self.agility)
- def pass_round(self):
- self.duration -= 0
- if self.has_expired():
- self.deapply()
- raise self.Expired()
- def has_expired(self):
- return self.duration == 0
- class Hero:
- def __init__(self, health, mana):
- self.attributes = HeroAttributes(health, mana)
- self.level = 0
- self.inventory = HeroInventory(capacity=30)
- self.equipment = HeroEquipment(self.attributes)
- self.buff = None
- def level_up(self):
- self.level += 1
- self.attributes.increase(1, 1, 1)
- def attack(self) -> int:
- """
- Returns the attack damage of the Hero
- """
- return self.attributes.damage
- def take_damage(self, damage: int):
- self.attributes.health -= damage
- def take_buff(self, buff: HeroBuff):
- self.buff = buff.with_attributes(self.attributes)
- self.buff.apply()
- def pass_round(self):
- if self.buff:
- try:
- self.buff.pass_round()
- except HeroBuff.Expired:
- self.buff = None
- def is_alive(self):
- return self.attributes.health > 0
- def take_item(self, item: Item):
- self.inventory.store_item(item)
- def equip_item(self, item: Item):
- if not self.inventory.has_item(item):
- raise Exception('Item is not present in inventory!')
- self.equipment.equip_item(item)
現(xiàn)在,在將 Hero 對象分解為 HeroAttributes、HeroInventory、HeroEquipment 和 HeroBuff 對象之后,未來新增功能就更加容易、更具有封裝性、具有更好的抽象,這份代碼也就越來越清晰了。
下面是三種分解關(guān)系:
- 關(guān)聯(lián):在兩個(gè)組成部分之間定義一個(gè)松弛的關(guān)系。兩個(gè)組成部分不互相依賴,但是可以一起工作。例如 Hero 對象和 Zone 對象。
- 聚合:在整體和部分之間定義一個(gè)弱「包含」關(guān)系。這種關(guān)系比較弱,因?yàn)椴糠挚梢栽跊]有整體的時(shí)候存在。例如 HeroInventory(英雄財(cái)產(chǎn))和 Item(條目)。HeroInventory 可以有很多 Items,而且一個(gè) Items 也可以屬于任何 HeroInventory(例如交易條目)。
- 組成:一個(gè)強(qiáng)「包含」關(guān)系,其中整體和部分不能彼此分離。部分不能被共享,因?yàn)檎w要依賴于這些特定的部分。例如 Hero(英雄)和 HeroAttributes(英雄屬性)。
4. 泛化
泛化可能是最重要的設(shè)計(jì)原則,即我們提取共享特征,并將它們結(jié)合到一起的過程。我們都知道函數(shù)和類的繼承,這就是一種泛化。
做一個(gè)比較可能會(huì)將這個(gè)解釋得更加清楚:盡管抽象通過隱藏非必需的細(xì)節(jié)減少了復(fù)雜性,但是泛化通過用一個(gè)單獨(dú)構(gòu)造體來替代多個(gè)執(zhí)行類似功能的實(shí)體。
- # Two methods which share common characteristics
- def take_physical_damage(self, physical_damage):
- print(f'Took {physical_damage} physical damage')
- self._health -= physical_damage
- def take_spell_damage(self, spell_damage):
- print(f'Took {spell_damage} spell damage')
- self._health -= spell_damage
- # vs.
- # One generalized method
- def take_damage(self, damage, is_physical=True):
- damage_type = 'physical' if is_physical else 'spell'
- print(f'Took {damage} {damage_type} damage')
- self._health -= damage
以上是函數(shù)示例,這種方法缺少泛化性能,而下面展示了具有泛化性能的案例。
- class Entity:
- def __init__(self):
- raise Exception('Should not be initialized directly!')
- def attack(self) -> int:
- """
- Returns the attack damage of the Hero
- """
- return self.attributes.damage
- def take_damage(self, damage: int):
- self.attributes.health -= damage
- def is_alive(self):
- return self.attributes.health > 0
- class Hero(Entity):
- pass
- class NPC(Entity):
- pass
這里,我們通過將它們的共同功能移動(dòng)到基本類中來減少復(fù)雜性,而不是讓 NPC 類和 Hero 類將所有的功能都實(shí)現(xiàn)兩次。
我們可能會(huì)過度使用繼承,因此很多有經(jīng)驗(yàn)的人都建議我們更偏向使用組合(Composition)而不是繼承(https://stackoverflow.com/a/53354)。
繼承常常被沒有經(jīng)驗(yàn)的程序員濫用,這可能是由于繼承是他們首先掌握的 OOP 技術(shù)。
5. 組合
組合就是把多個(gè)對象結(jié)合為一個(gè)更復(fù)雜對象的過程。這種方法會(huì)創(chuàng)建對象的示例,并且使用它們的功能,而不是直接繼承它。
使用組合原則的對象就被稱作組合對象(composite object)。這種組合對象在要比所有組成部分都簡單,這是非常重要的一點(diǎn)。當(dāng)把多個(gè)類結(jié)合成一個(gè)類的時(shí)候,我們希望把抽象的層次提高一些,讓對象更加簡單。
組合對象的 API 必須隱藏它的內(nèi)部模塊,以及內(nèi)部模塊之間的交互。就像一個(gè)機(jī)械時(shí)鐘,它有三個(gè)展示時(shí)間的指針,以及一個(gè)設(shè)置時(shí)間的旋鈕,但是它內(nèi)部包含很多運(yùn)動(dòng)的獨(dú)立部件。
正如我所說的,組合要優(yōu)于繼承,這意味著我們應(yīng)該努力將共用功能移動(dòng)到一個(gè)獨(dú)立的對象中,然后其它類就使用這個(gè)對象的功能,而不是將它隱藏在所繼承的基本類中。
讓我們闡述一下過度使用繼承功能的一個(gè)可能會(huì)發(fā)生的問題,現(xiàn)在我們僅僅向游戲中增加一個(gè)行動(dòng):
- class Entity:
- def __init__(self, x, y):
- self.x = x
- self.y = y
- raise Exception('Should not be initialized directly!')
- def attack(self) -> int:
- """
- Returns the attack damage of the Hero
- """
- return self.attributes.damage
- def take_damage(self, damage: int):
- self.attributes.health -= damage
- def is_alive(self):
- return self.attributes.health > 0
- def move_left(self):
- self.x -= 1
- def move_right(self):
- self.x += 1
- class Hero(Entity):
- pass
- class NPC(Entity):
- pass
好了,如果我們想在游戲中引入坐騎呢?坐騎也應(yīng)該需要左右移動(dòng),但是它沒有攻擊的能力,甚至沒有生命值。
我們的解決方案可能是簡單地將 move 邏輯移動(dòng)到獨(dú)立的 MoveableEntity 或者 MoveableObject 類中,這種類僅僅含有那項(xiàng)功能。
那么,如果我們想讓坐騎具有生命值,但是無法攻擊,那該怎么辦呢?希望你可以看到類的層次結(jié)構(gòu)是如何變得復(fù)雜的,即使我們的業(yè)務(wù)邏輯還是相當(dāng)簡單。
一個(gè)從某種程度來說比較好的方法是將動(dòng)作邏輯抽象為 Movement 類(或者其他更好的名字),并且在可能需要的類里面把它實(shí)例化。這將會(huì)很好地封裝函數(shù),并使其在所有種類的對象中都可以重用,而不僅僅局限于實(shí)體類。
6. 批判性思考
盡管這些設(shè)計(jì)原則是在數(shù)十年經(jīng)驗(yàn)中形成的,但盲目地將這些原則應(yīng)用到代碼之前進(jìn)行批判性思考是很重要的。
任何事情都是過猶不及!有時(shí)候這些原則可以走得很遠(yuǎn),但是實(shí)際上有時(shí)會(huì)變成一些很難使用的東西。
作為一個(gè)工程師,我們需要根據(jù)獨(dú)特的情境去批判地評價(jià)最好的方法,而不是盲目地遵從并應(yīng)用任意的原則。
三、關(guān)注點(diǎn)的內(nèi)聚、耦合和分離
1. 內(nèi)聚(Cohesion)
內(nèi)聚代表的是模塊內(nèi)部責(zé)任的分明,或者是模塊的復(fù)雜度。
如果我們的類只執(zhí)行一個(gè)任務(wù),而沒有其它明確的目標(biāo),那么這個(gè)類就有著高度內(nèi)聚性。另一方面,如果從某種程度而言它在做的事情并不清楚,或者具有多于一個(gè)的目標(biāo),那么它的內(nèi)聚性就非常低。
我們希望代碼具有較高的內(nèi)聚性,如果發(fā)現(xiàn)它們有非常多的目標(biāo),或許我們應(yīng)該將它們分割出來。
2. 耦合
耦合獲取的是連接不同類的復(fù)雜度。我們希望類與其它的類具有盡可能少、盡可能簡單的聯(lián)系,所以我們就可以在未來的事件中交換它們(例如改變網(wǎng)絡(luò)框架)。
在很多編程語言中,這都是通過大量使用接口來實(shí)現(xiàn)的,它們抽象出處理特定邏輯的類,然后表征為一種適配層,每個(gè)類都可以嵌入其中。
3. 分離關(guān)注點(diǎn)
分離關(guān)注點(diǎn)(SoC)是這樣一種思想:軟件系統(tǒng)必須被分割為功能上互不重疊的部分?;蛘哒f關(guān)注點(diǎn)必須分布在不同的地方,其中關(guān)注點(diǎn)表示能夠?yàn)橐粋€(gè)問題提供解決方案。
網(wǎng)頁就是一個(gè)很好的例子,它具有三個(gè)層(信息層、表示層和行為層),這三個(gè)層被分為三個(gè)不同的地方(分別是 HTML,CSS,以及 JS)。
如果重新回顧一下我們的 RPG 例子,你會(huì)發(fā)現(xiàn)它在最開始具有很多關(guān)注點(diǎn)(應(yīng)用 buffs 來計(jì)算襲擊傷害、處理資產(chǎn)、裝備條目,以及管理屬性)。我們通過分解將那些關(guān)注點(diǎn)分割成更多的內(nèi)聚類,它們抽象并封裝了它們的細(xì)節(jié)。我們的 Hero 類現(xiàn)在僅僅作為一個(gè)組合對象,它比之前更加簡單。
四、結(jié)語
對小規(guī)模的代碼應(yīng)用這些原則可能看起來很復(fù)雜。但是事實(shí)上,對于未來想要開發(fā)和維護(hù)的任何一個(gè)軟件項(xiàng)目而言,這些規(guī)則都是必須的。在剛開始寫這種代碼會(huì)有些成本,但是從長期來看,它會(huì)回報(bào)以幾倍增長。
這些原則保證我們的系統(tǒng)更加:
- 可擴(kuò)展:高內(nèi)聚使得不用關(guān)心不相關(guān)的功能就可以更容易地實(shí)現(xiàn)新模塊。
- 可維護(hù):低耦合保證一個(gè)模塊的改變通常不會(huì)影響其它模塊。高內(nèi)聚保證一個(gè)系統(tǒng)需求的改變只需要更改盡可能少的類。
- 可重用:高內(nèi)聚保證一個(gè)模塊的功能是完整的,也是被妥善定義的。低耦合使得模塊盡可能少地依賴系統(tǒng)的其它部分,這使得模塊在其它軟件中的重用變得更加容易。
在本文中,我們首先介紹了一些高級(jí)對象的類別(實(shí)體對象、邊界對象以及控制對象)。然后我們了解了一些構(gòu)建對象時(shí)使用的關(guān)鍵原則,比如抽象、泛化、分解和封裝等。最后,我們引入了兩個(gè)軟件質(zhì)量指標(biāo)(耦合和內(nèi)聚),然后學(xué)習(xí)了使用這些原則能夠帶來的好處。
我希望這篇文章提供了一些關(guān)于設(shè)計(jì)原則的概覽,如果我們希望自己能夠在這個(gè)領(lǐng)域獲得更多的進(jìn)步,我們還需要了解更多具體的操作。
原文地址:
https://medium.freecodecamp.org/a-short-overview-of-object-oriented-software-design-c7aa0a622c83
【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號(hào)“機(jī)器之心( id: almosthuman2014)”】