自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何寫一手漂亮的模型:面向?qū)ο缶幊痰脑O(shè)計(jì)原則綜述

開發(fā) 開發(fā)工具
本文介紹了算法實(shí)現(xiàn)中使用類和方法來構(gòu)建模型所需要注意的設(shè)計(jì)原則,它們可以讓我們的機(jī)器學(xué)習(xí)代碼更加美麗迷人。

面向?qū)ο蟮木幊淘趯?shí)現(xiàn)想法乃至系統(tǒng)的過程中都非常重要,我們不論是使用 TensorFlow 還是 PyTorch 來構(gòu)建模型都或多或少需要使用類和方法。而采用類的方法來構(gòu)建模型會(huì)令代碼非常具有可讀性和條理性,本文介紹了算法實(shí)現(xiàn)中使用類和方法來構(gòu)建模型所需要注意的設(shè)計(jì)原則,它們可以讓我們的機(jī)器學(xué)習(xí)代碼更加美麗迷人。

[[228012]]

大多數(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í)體對象。

  1. class Hero: 
  2.     def __init__(self, health, mana): 
  3.         self._health = health 
  4.         self._mana = mana 
  5.  
  6.     def attack(self) -> int: 
  7.         """ 
  8.         Returns the attack damage of the Hero 
  9.         """ 
  10.         return 1 
  11.  
  12.     def take_damage(self, damage: int): 
  13.         self._health -damage 
  14.  
  15.     def is_alive(self): 
  16.         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)。

  1. class Fight: 
  2.     class FightOver(Exception): 
  3.         def __init__(self, winner, *args, **kwargs): 
  4.             self.winner = winner 
  5.             super(*args, **kwargs) 
  6.  
  7.     def __init__(self, hero_a: Hero, hero_b: Hero): 
  8.         self._hero_a = hero_a 
  9.         self._hero_b = hero_b 
  10.         self.fight_ongoing = True 
  11.         self.winner = None 
  12.  
  13.     def fight(self): 
  14.         while self.fight_ongoing: 
  15.             self._run_round() 
  16.         print(f'The fight has ended! Winner is #{self.winner}') 
  17.  
  18.     def _run_round(self): 
  19.         try: 
  20.             self._run_attack(self._hero_a, self._hero_b) 
  21.             self._run_attack(self._hero_b, self._hero_a) 
  22.         except self.FightOver as e: 
  23.             self._finish_round(e.winner) 
  24.  
  25.     def _run_attack(self, attacker: Hero, victim: Hero): 
  26.         damage = attacker.attack() 
  27.         victim.take_damage(damage) 
  28.         if not victim.is_alive(): 
  29.             raise self.FightOver(winner=attacker
  30.  
  31.     def _finish_round(self, winner: Hero): 
  32.         self.winner = winner 
  33.         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ù)庫。

  1. class UserInput: 
  2.     def __init__(self, input_parser): 
  3.         self.input_parser = input_parser 
  4.  
  5.     def take_command(self): 
  6.         """ 
  7.         Takes the user's input, parses it into a recognizable command and returns it 
  8.         """ 
  9.         command = self._parse_input(self._take_input()) 
  10.         return command 
  11.  
  12.     def _parse_input(self, input): 
  13.         return self.input_parser.parse(input) 
  14.  
  15.     def _take_input(self): 
  16.         raise NotImplementedError() 
  17.  
  18. class UserMouseInput(UserInput): 
  19.     pass 
  20.  
  21. class UserKeyboardInput(UserInput): 
  22.     pass 
  23.  
  24. class UserJoystickInput(UserInput): 
  25.     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)這些功能。

  1. class Money: 
  2.     def __init__(self, gold, silver, copper): 
  3.         self.gold = gold 
  4.         self.silver = silver 
  5.         self.copper = copper 
  6.  
  7.     def __eq__(self, other): 
  8.         return self.gold == other.gold and self.silver == other.silver and self.copper == other.copper 
  9.  
  10.     def __gt__(self, other): 
  11.         if self.gold == other.gold and self.silver == other.silver: 
  12.             return self.copper > other.copper 
  13.         if self.gold == other.gold: 
  14.             return self.silver > other.silver 
  15.  
  16.         return self.gold > other.gold 
  17.  
  18.     def __add__(self, other): 
  19.         return Money(gold=self.gold + other.gold, silver=self.silver + other.silver, copper=self.copper + other.copper) 
  20.  
  21.     def __str__(self): 
  22.         return f'Money Object(Gold: {self.gold}; Silver: {self.silver}; Copper: {self.copper})' 
  23.  
  24.     def __repr__(self): 
  25.         return self.__str__() 
  26.  
  27.  
  28. print(Money(1, 1, 1) == Money(1, 1, 1)) 
  29. # => True 
  30. print(Money(1, 1, 1) > Money(1, 2, 1)) 
  31. # => False 
  32. print(Money(1, 1, 0) + Money(1, 1, 1)) 
  33. # => 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),裝備,角色屬性。

  1. class Hero: 
  2.     def __init__(self, health, mana): 
  3.         self._health = health 
  4.         self._mana = mana 
  5.         self._strength = 0 
  6.         self._agility = 0 
  7.         self._stamina = 0 
  8.         self.level = 0 
  9.         self._items = {} 
  10.         self._equipment = {} 
  11.         self._item_capacity = 30 
  12.         self.stamina_buff = None 
  13.         self.agility_buff = None 
  14.         self.strength_buff = None 
  15.         self.buff_duration = -1 
  16.  
  17.     def level_up(self): 
  18.         self.level += 1 
  19.         self._stamina += 1 
  20.         self._agility += 1 
  21.         self._strength += 1 
  22.         self._health += 5 
  23.  
  24.     def take_buff(self, stamina_increase, strength_increase, agility_increase): 
  25.         self.stamina_buff = stamina_increase 
  26.         self.agility_buff = agility_increase 
  27.         self.strength_buff = strength_increase 
  28.         self._stamina += stamina_increase 
  29.         self._strength += strength_increase 
  30.         self._agility += agility_increase 
  31.         self.buff_duration = 10  # rounds 
  32.  
  33.     def pass_round(self): 
  34.         if self.buff_duration > 0: 
  35.             self.buff_duration -1 
  36.         if self.buff_duration == 0:  # Remove buff 
  37.             self._stamina -self.stamina_buff 
  38.             self._strength -self.strength_buff 
  39.             self._agility -self.agility_buff 
  40.             self._health -self.stamina_buff * 5 
  41.             self.buff_duration = -1 
  42.             self.stamina_buff = None 
  43.             self.agility_buff = None 
  44.             self.strength_buff = None 
  45.  
  46.     def attack(self) -> int: 
  47.         """ 
  48.         Returns the attack damage of the Hero 
  49.         """ 
  50.         return 1 + (self._agility * 0.2) + (self._strength * 0.2) 
  51.  
  52.     def take_damage(self, damage: int): 
  53.         self._health -damage 
  54.  
  55.     def is_alive(self): 
  56.         return self._health > 0 
  57.  
  58.     def take_item(self, item: Item): 
  59.         if self._item_capacity == 0: 
  60.             raise Exception('No more free slots') 
  61.         self._items[item.id] = item 
  62.         self._item_capacity -1 
  63.  
  64.     def equip_item(self, item: Item): 
  65.         if item.id not in self._items: 
  66.             raise Exception('Item is not present in inventory!') 
  67.         self._equipment[item.slot] = item 
  68.         self._agility += item.agility 
  69.         self._stamina += item.stamina 
  70.         self._strength += item.strength 
  71.         self._health += item.stamina * 5 
  72. # 缺乏分解的案例 

我們可能會(huì)說這份代碼已經(jīng)開始變得相當(dāng)混亂了。

例如,我們的耐力分?jǐn)?shù)為 5 個(gè)生命值,如果將來要修改為 6 個(gè)生命值,我們就要在很多地方修改這個(gè)實(shí)現(xiàn)。

解決方案就是將 Hero 對象分解為多個(gè)更小的對象,每個(gè)小對象可承擔(dān)一些功能。下面展示了一個(gè)邏輯比較清晰的架構(gòu):

  1. from copy import deepcopy 
  2.  
  3. class AttributeCalculator: 
  4.     @staticmethod 
  5.     def stamina_to_health(self, stamina): 
  6.         return stamina * 6 
  7.  
  8.     @staticmethod 
  9.     def agility_to_damage(self, agility): 
  10.         return agility * 0.2 
  11.  
  12.     @staticmethod 
  13.     def strength_to_damage(self, strength): 
  14.         return strength * 0.2 
  15.  
  16. class HeroInventory: 
  17.     class FullInventoryException(Exception): 
  18.         pass 
  19.  
  20.     def __init__(self, capacity): 
  21.         self._equipment = {} 
  22.         self._item_capacity = capacity 
  23.  
  24.     def store_item(self, item: Item): 
  25.         if self._item_capacity < 0: 
  26.             raise self.FullInventoryException() 
  27.         self._equipment[item.id] = item 
  28.         self._item_capacity -1 
  29.  
  30.     def has_item(self, item): 
  31.         return item.id in self._equipment 
  32.  
  33. class HeroAttributes: 
  34.     def __init__(self, health, mana): 
  35.         self.health = health 
  36.         self.mana = mana 
  37.         self.stamina = 0 
  38.         self.strength = 0 
  39.         self.agility = 0 
  40.         self.damage = 1 
  41.  
  42.     def increase(self, stamina=0agility=0strength=0): 
  43.         self.stamina += stamina 
  44.         self.health += AttributeCalculator.stamina_to_health(stamina) 
  45.         self.damage += AttributeCalculator.strength_to_damage(strength) + AttributeCalculator.agility_to_damage(agility) 
  46.         self.agility += agility 
  47.         self.strength += strength 
  48.  
  49.     def decrease(self, stamina=0agility=0strength=0): 
  50.         self.stamina -stamina 
  51.         self.health -AttributeCalculator.stamina_to_health(stamina) 
  52.         self.damage -AttributeCalculator.strength_to_damage(strength) + AttributeCalculator.agility_to_damage(agility) 
  53.         self.agility -agility 
  54.         self.strength -strength 
  55.  
  56. class HeroEquipment: 
  57.     def __init__(self, hero_attributes: HeroAttributes): 
  58.         self.hero_attributes = hero_attributes 
  59.         self._equipment = {} 
  60.  
  61.     def equip_item(self, item): 
  62.         self._equipment[item.slot] = item 
  63.         self.hero_attributes.increase(stamina=item.stamina, strength=item.strength, agility=item.agility) 
  64.  
  65.  
  66. class HeroBuff: 
  67.     class Expired(Exception): 
  68.         pass 
  69.  
  70.     def __init__(self, stamina, strength, agility, round_duration): 
  71.         self.attributes = None 
  72.         self.stamina = stamina 
  73.         self.strength = strength 
  74.         self.agility = agility 
  75.         self.duration = round_duration 
  76.  
  77.     def with_attributes(self, hero_attributes: HeroAttributes): 
  78.         buff = deepcopy(self) 
  79.         buff.attributes = hero_attributes 
  80.         return buff 
  81.  
  82.     def apply(self): 
  83.         if self.attributes is None: 
  84.             raise Exception() 
  85.         self.attributes.increase(stamina=self.stamina, strength=self.strength, agility=self.agility) 
  86.  
  87.     def deapply(self): 
  88.         self.attributes.decrease(stamina=self.stamina, strength=self.strength, agility=self.agility) 
  89.  
  90.     def pass_round(self): 
  91.         self.duration -0 
  92.         if self.has_expired(): 
  93.             self.deapply() 
  94.             raise self.Expired() 
  95.  
  96.     def has_expired(self): 
  97.         return self.duration == 0 
  98.  
  99.  
  100. class Hero: 
  101.     def __init__(self, health, mana): 
  102.         self.attributes = HeroAttributes(health, mana) 
  103.         self.level = 0 
  104.         self.inventory = HeroInventory(capacity=30
  105.         self.equipment = HeroEquipment(self.attributes) 
  106.         self.buff = None 
  107.  
  108.     def level_up(self): 
  109.         self.level += 1 
  110.         self.attributes.increase(1, 1, 1) 
  111.  
  112.     def attack(self) -> int: 
  113.         """ 
  114.         Returns the attack damage of the Hero 
  115.         """ 
  116.         return self.attributes.damage 
  117.  
  118.     def take_damage(self, damage: int): 
  119.         self.attributes.health -damage 
  120.  
  121.     def take_buff(self, buff: HeroBuff): 
  122.         self.buff = buff.with_attributes(self.attributes) 
  123.         self.buff.apply() 
  124.  
  125.     def pass_round(self): 
  126.         if self.buff: 
  127.             try: 
  128.                 self.buff.pass_round() 
  129.             except HeroBuff.Expired: 
  130.                 self.buff = None 
  131.  
  132.     def is_alive(self): 
  133.         return self.attributes.health > 0 
  134.  
  135.     def take_item(self, item: Item): 
  136.         self.inventory.store_item(item) 
  137.  
  138.     def equip_item(self, item: Item): 
  139.         if not self.inventory.has_item(item): 
  140.             raise Exception('Item is not present in inventory!') 
  141.         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í)體。

  1. # Two methods which share common characteristics 
  2. def take_physical_damage(self, physical_damage): 
  3.     print(f'Took {physical_damage} physical damage') 
  4.     self._health -physical_damage 
  5.  
  6. def take_spell_damage(self, spell_damage): 
  7.     print(f'Took {spell_damage} spell damage') 
  8.     self._health -spell_damage 
  9.  
  10. # vs. 
  11.  
  12. # One generalized method 
  13. def take_damage(self, damage, is_physical=True): 
  14.     damage_type = 'physical' if is_physical else 'spell' 
  15.     print(f'Took {damage} {damage_type} damage') 
  16.     self._health -damage 

以上是函數(shù)示例,這種方法缺少泛化性能,而下面展示了具有泛化性能的案例。

  1. class Entity: 
  2.     def __init__(self): 
  3.         raise Exception('Should not be initialized directly!') 
  4.  
  5.     def attack(self) -> int: 
  6.         """ 
  7.         Returns the attack damage of the Hero 
  8.         """ 
  9.         return self.attributes.damage 
  10.  
  11.     def take_damage(self, damage: int): 
  12.         self.attributes.health -damage 
  13.  
  14.     def is_alive(self): 
  15.         return self.attributes.health > 0 
  16.  
  17.  
  18. class Hero(Entity): 
  19.     pass 
  20.  
  21. class NPC(Entity): 
  22.     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):

  1. class Entity: 
  2.     def __init__(self, x, y): 
  3.         self.x = x 
  4.         self.y = y 
  5.         raise Exception('Should not be initialized directly!') 
  6.  
  7.     def attack(self) -> int: 
  8.         """ 
  9.         Returns the attack damage of the Hero 
  10.         """ 
  11.         return self.attributes.damage 
  12.  
  13.     def take_damage(self, damage: int): 
  14.         self.attributes.health -damage 
  15.  
  16.     def is_alive(self): 
  17.         return self.attributes.health > 0 
  18.  
  19.     def move_left(self): 
  20.         self.x -1 
  21.  
  22.     def move_right(self): 
  23.         self.x += 1 
  24.  
  25.  
  26. class Hero(Entity): 
  27.     pass 
  28.  
  29. class NPC(Entity): 
  30.     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)”】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2011-05-26 09:39:53

程序

2009-01-16 08:52:26

面向?qū)ο?/a>OOP編程

2019-10-24 15:23:04

SQL優(yōu)化數(shù)據(jù)庫

2013-04-17 10:46:54

面向?qū)ο?/a>

2023-07-16 22:57:38

代碼場景業(yè)務(wù)

2012-06-07 10:11:01

面向?qū)ο?/a>設(shè)計(jì)原則Java

2019-12-16 14:04:48

MySQL數(shù)據(jù)庫SQL

2020-05-22 08:24:21

SQLMySQL數(shù)據(jù)庫

2023-11-10 16:08:23

SQL數(shù)據(jù)庫

2009-09-27 14:12:12

面向?qū)ο笤O(shè)計(jì)單一職責(zé)

2009-06-30 15:29:00

Java面向?qū)ο?/a>

2016-11-25 13:50:15

React組件SFC

2020-06-09 07:00:00

面向?qū)ο?/a>編程編程原則

2018-10-15 15:24:18

Python函數(shù)代碼

2009-06-17 14:38:14

面向?qū)ο?/a>數(shù)學(xué)模型物理模型

2011-07-12 17:53:21

PHP

2023-08-24 21:49:54

人工智能高端算法工程師

2024-05-10 09:28:57

Python面向?qū)ο?/a>代碼

2012-05-08 10:14:45

設(shè)計(jì)原則

2018-09-04 15:45:58

Python代碼編程語言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)