日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
如何寫一手漂亮的模型:面向?qū)ο缶幊痰脑O(shè)計(jì)原則綜述

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

十年的泗洪網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。全網(wǎng)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整泗洪建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)從事“泗洪網(wǎng)站設(shè)計(jì)”,“泗洪網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

大多數(shù)現(xiàn)代編程語言都支持并且鼓勵面向?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.     def attack(self) -> int:
  6.         """
  7.         Returns the attack damage of the Hero
  8.         """
  9.         return 1
  10.     def take_damage(self, damage: int):
  11.         self._health -= damage
  12.     def is_alive(self):
  13.         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.     def __init__(self, hero_a: Hero, hero_b: Hero):
  7.         self._hero_a = hero_a
  8.         self._hero_b = hero_b
  9.         self.fight_ongoing = True
  10.         self.winner = None
  11.     def fight(self):
  12.         while self.fight_ongoing:
  13.             self._run_round()
  14.         print(f'The fight has ended! Winner is #{self.winner}')
  15.     def _run_round(self):
  16.         try:
  17.             self._run_attack(self._hero_a, self._hero_b)
  18.             self._run_attack(self._hero_b, self._hero_a)
  19.         except self.FightOver as e:
  20.             self._finish_round(e.winner)
  21.     def _run_attack(self, attacker: Hero, victim: Hero):
  22.         damage = attacker.attack()
  23.         victim.take_damage(damage)
  24.         if not victim.is_alive():
  25.             raise self.FightOver(winner=attacker)
  26.     def _finish_round(self, winner: Hero):
  27.         self.winner = winner
  28.         self.fight_ongoing = False

在這種類中,為對戰(zhàn)封裝編程邏輯可以給我們提供多個(gè)好處:其中之一就是動作的可擴(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.     def take_command(self):
  5.         """
  6.         Takes the user's input, parses it into a recognizable command and returns it
  7.         """
  8.         command = self._parse_input(self._take_input())
  9.         return command
  10.     def _parse_input(self, input):
  11.         return self.input_parser.parse(input)
  12.     def _take_input(self):
  13.         raise NotImplementedError()
  14. class UserMouseInput(UserInput):
  15.     pass
  16. class UserKeyboardInput(UserInput):
  17.     pass
  18. class UserJoystickInput(UserInput):
  19.     pass

這些邊界對象負(fù)責(zé)向系統(tǒng)內(nèi)部或者外部傳遞信息。例如對要接收的用戶指令,我們需要一個(gè)邊界對象來將鍵盤輸入(比如一個(gè)空格鍵)轉(zhuǎn)換為一個(gè)可識別的域事件(例如角色的跳躍)。

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.     def __eq__(self, other):
  7.         return self.gold == other.gold and self.silver == other.silver and self.copper == other.copper
  8.     def __gt__(self, other):
  9.         if self.gold == other.gold and self.silver == other.silver:
  10.             return self.copper > other.copper
  11.         if self.gold == other.gold:
  12.             return self.silver > other.silver
  13.         return self.gold > other.gold
  14.     def __add__(self, other):
  15.         return Money(gold=self.gold + other.gold, silver=self.silver + other.silver, copper=self.copper + other.copper)
  16.     def __str__(self):
  17.         return f'Money Object(Gold: {self.gold}; Silver: {self.silver}; Copper: {self.copper})'
  18.     def __repr__(self):
  19.         return self.__str__()
  20. print(Money(1, 1, 1) == Money(1, 1, 1))
  21. # => True
  22. print(Money(1, 1, 1) > Money(1, 2, 1))
  23. # => False
  24. print(Money(1, 1, 0) + Money(1, 1, 1))
  25. # => 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ù)不會做一些異常的事情,例如在還沒死亡的時(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ā)生了意外。我們將會知道,我們唯一可能破壞的代碼就是 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.     def level_up(self):
  17.         self.level += 1
  18.         self._stamina += 1
  19.         self._agility += 1
  20.         self._strength += 1
  21.         self._health += 5
  22.     def take_buff(self, stamina_increase, strength_increase, agility_increase):
  23.         self.stamina_buff = stamina_increase
  24.         self.agility_buff = agility_increase
  25.         self.strength_buff = strength_increase
  26.         self._stamina += stamina_increase
  27.         self._strength += strength_increase
  28.         self._agility += agility_increase
  29.         self.buff_duration = 10  # rounds
  30.     def pass_round(self):
  31.         if self.buff_duration > 0:
  32.             self.buff_duration -= 1
  33.         if self.buff_duration == 0:  # Remove buff
  34.             self._stamina -= self.stamina_buff
  35.             self._strength -= self.strength_buff
  36.             self._agility -= self.agility_buff
  37.             self._health -= self.stamina_buff * 5
  38.             self.buff_duration = -1
  39.             self.stamina_buff = None
  40.             self.agility_buff = None
  41.             self.strength_buff = None
  42.     def attack(self) -> int:
  43.         """
  44.         Returns the attack damage of the Hero
  45.         """
  46.         return 1 + (self._agility * 0.2) + (self._strength * 0.2)
  47.     def take_damage(self, damage: int):
  48.         self._health -= damage
  49.     def is_alive(self):
  50.         return self._health > 0
  51.     def take_item(self, item: Item):
  52.         if self._item_capacity == 0:
  53.             raise Exception('No more free slots')
  54.         self._items[item.id] = item
  55.         self._item_capacity -= 1
  56.     def equip_item(self, item: Item):
  57.         if item.id not in self._items:
  58.             raise Exception('Item is not present in inventory!')
  59.         self._equipment[item.slot] = item
  60.         self._agility += item.agility
  61.         self._stamina += item.stamina
  62.         self._strength += item.strength
  63.         self._health += item.stamina * 5
  64. # 缺乏分解的案例

我們可能會說這份代碼已經(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. class AttributeCalculator:
  3.     @staticmethod
  4.     def stamina_to_health(self, stamina):
  5.         return stamina * 6
  6.     @staticmethod
  7.     def agility_to_damage(self, agility):
  8.         return agility * 0.2
  9.     @staticmethod
  10.     def strength_to_damage(self, strength):
  11.         return strength * 0.2
  12. class HeroInventory:
  13.     class FullInventoryException(Exception):
  14.         pass
  15.     def __init__(self, capacity):
  16.         self._equipment = {}
  17.         self._item_capacity = capacity
  18.     def store_item(self, item: Item):
  19.         if self._item_capacity < 0:
  20.             raise self.FullInventoryException()
  21.         self._equipment[item.id] = item
  22.         self._item_capacity -= 1
  23.     def has_item(self, item):
  24.         return item.id in self._equipment
  25. class HeroAttributes:
  26.     def __init__(self, health, mana):
  27.         self.health = health
  28.         self.mana = mana
  29.         self.stamina = 0
  30.         self.strength = 0
  31.         self.agility = 0
  32.         self.damage = 1
  33.     def increase(self, stamina=0, agility=0, strength=0):
  34.         self.stamina += stamina
  35.         self.health += AttributeCalculator.stamina_to_health(stamina)
  36.         self.damage += AttributeCalculator.strength_to_damage(strength) + AttributeCalculator.agility_to_damage(agility)
  37.         self.agility += agility
  38.         self.strength += strength
  39.     def decrease(self, stamina=0, agility=0, strength=0):
  40.         self.stamina -= stamina
  41.         self.health -= AttributeCalculator.stamina_to_health(stamina)
  42.         self.damage -= AttributeCalculator.strength_to_damage(strength) + AttributeCalculator.agility_to_damage(agility)
  43.         self.agility -= agility
  44.         self.strength -= strength
  45. class HeroEquipment:
  46.     def __init__(self, hero_attributes: HeroAttributes):
  47.         self.hero_attributes = hero_attributes
  48.         self._equipment = {}
  49.     def equip_item(self, item):
  50.         self._equipment[item.slot] = item
  51.         self.hero_attributes.increase(stamina=item.stamina, strength=item.strength, agility=item.agility)
  52. class HeroBuff:
  53.     class Expired(Exception):
  54.         pass
  55.     def __init__(self, stamina, strength, agility, round_duration):
  56.         self.attributes = None
  57.         self.stamina = stamina
  58.         self.strength = strength
  59.         self.agility = agility
  60.         self.duration = round_duration
  61.     def with_attributes(self, hero_attributes: HeroAttributes):
  62.         buff = deepcopy(self)
  63.         buff.attributes = hero_attributes
  64.         return buff
  65.     def apply(self):
  66.         if self.attributes is None:
  67.             raise Exception()
  68.         self.attributes.increase(stamina=self.stamina, strength=self.strength, agility=self.agility)
  69.     def deapply(self):
  70.         self.attributes.decrease(stamina=self.stamina, strength=self.strength, agility=self.agility)
  71.     def pass_round(self):
  72.         self.duration -= 0
  73.         if self.has_expired():
  74.             self.deapply()
  75.             raise self.Expired()
  76.     def has_expired(self):
  77.         return self.duration == 0
  78. class Hero:
  79.     def __init__(self, health, mana):
  80.         self.attributes = HeroAttributes(health, mana)
  81.         self.level = 0
  82.         self.inventory = HeroInventory(capacity=30)
  83.         self.equipment = HeroEquipment(self.attributes)
  84.         self.buff = None
  85.     def level_up(self):
  86.         self.level += 1
  87.         self.attributes.increase(1, 1, 1)
  88.     def attack(self) -> int:
  89.         """
  90.         Returns the attack damage of the Hero
  91.         """
  92.         return self.attributes.damage
  93.     def take_damage(self, damage: int):
  94.         self.attributes.health -= damage
  95.     def take_buff(self, buff: HeroBuff):
  96.         self.buff = buff.with_attributes(self.attributes)
  97.         self.buff.apply()
  98.     def pass_round(self):
  99.         if self.buff:
  100.             try:
  101.                 self.buff.pass_round()
  102.             except HeroBuff.Expired:
  103.                 self.buff = None
  104.     def is_alive(self):
  105.         return self.attributes.health > 0
  106.     def take_item(self, item: Item):
  107.         self.inventory.store_item(item)
  108.     def equip_item(self, item: Item):
  109.         if not self.inventory.has_item(item):
  110.             raise Exception('Item is not present in inventory!')
  111.         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è)比較可能會將這個(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. def take_spell_damage(self, spell_damage):
  6.     print(f'Took {spell_damage} spell damage')
  7.     self._health -= spell_damage
  8. # vs.
  9. # One generalized method
  10. def take_damage(self, damage, is_physical=True):
  11.     damage_type = 'physical' if is_physical else 'spell'
  12.     print(f'Took {damage} {damage_type} damage')
  13.     self._health -= damage

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

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

這里,我們通過將它們的共同功能移動到基本類中來減少復(fù)雜性,而不是讓 NPC 類和 Hero 類將所有的功能都實(shí)現(xiàn)兩次。

我們可能會過度使用繼承,因此很多有經(jīng)驗(yàn)的人都建議我們更偏向使用組合(Composition)而不是繼承(https://stackoverflow.com/a/53354)。

繼承常常被沒有經(jīng)驗(yàn)的程序員濫用,這可能是由于繼承是他們首先掌握的 OOP 技術(shù)。

5. 組合

組合就是把多個(gè)對象結(jié)合為一個(gè)更復(fù)雜對象的過程。這種方法會創(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ú)立部件。

正如我所說的,組合要優(yōu)于繼承,這意味著我們應(yīng)該努力將共用功能移動到一個(gè)獨(dú)立的對象中,然后其它類就使用這個(gè)對象的功能,而不是將它隱藏在所繼承的基本類中。

讓我們闡述一下過度使用繼承功能的一個(gè)可能會發(fā)生的問題,現(xiàn)在我們僅僅向游戲中增加一個(gè)行動:

 
 
 
 
  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.     def attack(self) -> int:
  7.         """
  8.         Returns the attack damage of the Hero
  9.         """
  10.         return self.attributes.damage
  11.     def take_damage(self, damage: int):
  12.         self.attributes.health -= damage
  13.     def is_alive(self):
  14.         return self.attributes.health > 0
  15.     def move_left(self):
  16.         self.x -= 1
  17.     def move_right(self):
  18.         self.x += 1
  19. class Hero(Entity):
  20.     pass
  21. class NPC(Entity):
  22.     pass

好了,如果我們想在游戲中引入坐騎呢?坐騎也應(yīng)該需要左右移動,但是它沒有攻擊的能力,甚至沒有生命值。

我們的解決方案可能是簡單地將 move 邏輯移動到獨(dú)立的 MoveableEntity 或者 MoveableObject 類中,這種類僅僅含有那項(xiàng)功能。

那么,如果我們想讓坐騎具有生命值,但是無法攻擊,那該怎么辦呢?希望你可以看到類的層次結(jié)構(gòu)是如何變得復(fù)雜的,即使我們的業(yè)務(wù)邏輯還是相當(dāng)簡單。

一個(gè)從某種程度來說比較好的方法是將動作邏輯抽象為 Movement 類(或者其他更好的名字),并且在可能需要的類里面把它實(shí)例化。這將會很好地封裝函數(shù),并使其在所有種類的對象中都可以重用,而不僅僅局限于實(shí)體類。

6. 批判性思考

盡管這些設(shè)計(jì)原則是在數(shù)十年經(jīng)驗(yàn)中形成的,但盲目地將這些原則應(yīng)用到代碼之前進(jìn)行批判性思考是很重要的。

任何事情都是過猶不及!有時(shí)候這些原則可以走得很遠(yuǎn),但是實(shí)際上有時(shí)會變成一些很難使用的東西。

作為一個(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 例子,你會發(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ī)則都是必須的。在剛開始寫這種代碼會有些成本,但是從長期來看,它會回報(bào)以幾倍增長。

這些原則保證我們的系統(tǒng)更加:

  • 可擴(kuò)展:高內(nèi)聚使得不用關(guān)心不相關(guān)的功能就可以更容易地實(shí)現(xiàn)新模塊。
  • 可維護(hù):低耦合保證一個(gè)模塊的改變通常不會影響其它模塊。高內(nèi)聚保證一個(gè)系統(tǒng)需求的改變只需要更改盡可能少的類。
  • 可重用:高內(nèi)聚保證一個(gè)模塊的功能是完整的,也是被妥善定義的。低耦合使得模塊盡可能少地依賴系統(tǒng)的其它部分,這使得模塊在其它軟件中的重用變得更加容易。

在本文中,我們首先介紹了一些高級對象的類別(實(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

【本文是專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號“機(jī)器之心( id: almosthuman2014)”】


網(wǎng)頁名稱:如何寫一手漂亮的模型:面向?qū)ο缶幊痰脑O(shè)計(jì)原則綜述
轉(zhuǎn)載來源:http://m.5511xx.com/article/cojssgc.html