如何寫一手漂亮的模型:面向對象編程的設計原則綜述

2022-01-04 機器之心

面向對象的編程在實現想法乃至系統的過程中都非常重要,我們不論是使用 TensorFlow 還是 PyTorch 來構建模型都或多或少需要使用類和方法。而採用類的方法來構建模型會令代碼非常具有可讀性和條理性,本文介紹了算法實現中使用類和方法來構建模型所需要注意的設計原則,它們可以讓我們的機器學習代碼更加美麗迷人。

大多數現代程式語言都支持並且鼓勵面向對象編程(OOP)。即使我們最近似乎看到了一些偏離,因為人們開始使用不太受 OOP 影響的程式語言(例如 Go, Rust, Elixir, Elm, Scala),但是大多數還是具有面向對象的屬性。我們在這裡概括出的設計原則也適用於非 OOP 程式語言。

為了成功地寫出清晰的、高質量的、可維護並且可擴展的代碼,我們需要以 Python 為例了解在過去數十年裡被證明是有效的設計原則。

對象類型

因為我們要圍繞對象來建立代碼,所以區分它們的不同責任和變化是有用的。一般來說,面向對象的編程有三種類型的對象。

1. 實體對象

這類對象通常對應著問題空間中的一些現實實體。比如我們要建立一個角色扮演遊戲(RPG),那麼簡單的 Hero 類就是一個實體對象。

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

這類對象通常包含關於它們自身的屬性(例如 health 或 mana),這些屬性根據具體的規則都是可修改的。

2. 控制對象(Control Object)

控制對象(有時候也稱作管理對象)主要負責與其它對象的協調,這是一些管理並調用其它對象的對象。我們上面的 RPG 案例中有一個很棒的例子,Fight 類控制兩個英雄,並讓它們對戰。

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

在這種類中,為對戰封裝編程邏輯可以給我們提供多個好處:其中之一就是動作的可擴展性。我們可以很容易地將參與戰鬥的英雄傳遞給非玩家角色(NPC),這樣它們就能利用相同的 API。我們還可以很容易地繼承這個類,並複寫一些功能來滿足新的需要。

3. 邊界對象(Boundary Object)

這些是處在系統邊緣的對象。任何一個從其它系統獲取輸入或者給其它系統產生輸出的對象都可以被歸類為邊界對象,無論那個系統是用戶,網際網路或者是資料庫。

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

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

Bonus:值對象(Value Object)

價值對象代表的是域(domain)中的一個簡單值。它們無法改變,不恆一。

如果將它們結合在我們的遊戲中,Money 類或者 Damage 類就表示這種對象。上述的對象讓我們容易地區分、尋找和調試相關功能,然而僅使用基礎的整形數組或者整數卻無法實現這些功能。

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))

print(Money(1, 1, 1) > Money(1, 2, 1))

print(Money(1, 1, 0) + Money(1, 1, 1))

它們可以歸類為實體對象的子類別。

關鍵設計原則

設計原則是軟體設計中的規則,過去這些年裡已經證明它們是有價值的。嚴格地遵循這些原則有助於軟體達到一流的質量。

抽象(Abstraction)

抽象就是將一個概念在一定的語境中簡化為原始本質的一種思想。它允許我們拆解一個概念來更好的理解它。

上面的遊戲案例闡述了抽象,讓我們來看一下 Fight 類是如何構建的。我們以儘可能簡單的方式使用它,即在實例化的過程中給它兩個英雄作為參數,然後調用 fight() 方法。不多也不少,就這些。

代碼中的抽象過程應該遵循最少意外(POLA)的原則,抽象不應該用不必要和不相關的行為/屬性。換句話說,它應該是直觀的。

注意,我們的 Hero#take_damage() 函數不會做一些異常的事情,例如在還沒死亡的時候刪除角色。但是如果他的生命值降到零以下,我們可以期望它來殺死我們的角色。

封裝

封裝可以被認為是將某些東西放在一個類以內,並限制了它向外部展現的信息。在軟體中,限制對內部對象和屬性的訪問有助於保證數據的完整性。

將內部編程邏輯封裝成黑盒子,我們的類將更容易管理,因為我們知道哪部分可以被其它系統使用,哪些不行。這意味著我們在保留公共部分並且保證不破壞任何東西的同時能夠重用內部邏輯。此外,我們從外部使用封裝功能變得更加簡單,因為需要考慮的事情也更少。

在大多數程式語言中,封裝都是通過所謂的 Access modifiers(訪問控制修飾符)來完成的(例如 private,protected 等等)。Python 並不是這方面的最佳例子,因為它不能在運行時構建這種顯式修飾符,但是我們使用約定來解決這個問題。變量和函數前面的_前綴就意味著它們是私有的。

舉個例子,試想將我們的 Fight#_run_attack 方法修改為返回一個布爾變量,這意味著戰鬥結束而不是發生了意外。我們將會知道,我們唯一可能破壞的代碼就是 Fight 類的內部,因為我們是把這個函數設置為私有的。

請記住,代碼更多的是被修改而不是重寫。能夠儘可能清晰、較小影響的方式修改代碼對開發的靈活性很重要。

分解

分解就是把一個對象分割為多個更小的獨立部分,這些獨立的部分更易於理解、維護和編程。

試想我們現在希望 Hero 類能結合更多的 RPG 特徵,例如 buffs,資產,裝備,角色屬性。

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  

   def pass_round(self):
       if self.buff_duration > 0:
           self.buff_duration -= 1
       if self.buff_duration == 0:  
           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
# 缺乏分解的案例

我們可能會說這份代碼已經開始變得相當混亂了。我們的 Hero 對象一次性設置了太多的屬性,結果導致這份代碼變得相當脆弱。

例如,我們的耐力分數為 5 個生命值,如果將來要修改為 6 個生命值,我們就要在很多地方修改這個實現。

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

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)

現在,在將 Hero 對象分解為 HeroAttributes、HeroInventory、HeroEquipment 和 HeroBuff 對象之後,未來新增功能就更加容易、更具有封裝性、具有更好的抽象,這份代碼也就越來越清晰了。

下面是三種分解關係:

關聯:在兩個組成部分之間定義一個鬆弛的關係。兩個組成部分不互相依賴,但是可以一起工作。例如 Hero 對象和 Zone 對象。

聚合:在整體和部分之間定義一個弱「包含」關係。這種關係比較弱,因為部分可以在沒有整體的時候存在。例如 HeroInventory(英雄財產)和 Item(條目)。HeroInventory 可以有很多 Items,而且一個 Items 也可以屬於任何 HeroInventory(例如交易條目)。

組成:一個強「包含」關係,其中整體和部分不能彼此分離。部分不能被共享,因為整體要依賴於這些特定的部分。例如 Hero(英雄)和 HeroAttributes(英雄屬性)。

泛化

泛化可能是最重要的設計原則,即我們提取共享特徵,並將它們結合到一起的過程。我們都知道函數和類的繼承,這就是一種泛化。

做一個比較可能會將這個解釋得更加清楚:儘管抽象通過隱藏非必需的細節減少了複雜性,但是泛化通過用一個單獨構造體來替代多個執行類似功能的實體。


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




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
   

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

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

在給出的例子中,我們將常用的 Hero 類和 NPC 類泛化為一個共同的父類 Entity,並通過繼承簡化子類的構建。

這裡,我們通過將它們的共同功能移動到基本類中來減少複雜性,而不是讓 NPC 類和 Hero 類將所有的功能都實現兩次。

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

繼承常常被沒有經驗的程式設計師濫用,這可能是由於繼承是他們首先掌握的 OOP 技術。

組合

組合就是把多個對象結合為一個更複雜對象的過程。這種方法會創建對象的示例,並且使用它們的功能,而不是直接繼承它。

使用組合原則的對象就被稱作組合對象(composite object)。這種組合對象在要比所有組成部分都簡單,這是非常重要的一點。當把多個類結合成一個類的時候,我們希望把抽象的層次提高一些,讓對象更加簡單。

組合對象的 API 必須隱藏它的內部模塊,以及內部模塊之間的交互。就像一個機械時鐘,它有三個展示時間的指針,以及一個設置時間的旋鈕,但是它內部包含很多運動的獨立部件。

正如我所說的,組合要優於繼承,這意味著我們應該努力將共用功能移動到一個獨立的對象中,然後其它類就使用這個對象的功能,而不是將它隱藏在所繼承的基本類中。

讓我們闡述一下過度使用繼承功能的一個可能會發生的問題,現在我們僅僅向遊戲中增加一個行動:

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

正如我們所學到的,我們將 move_right 和 move_left 移動到 Entity 類中,而不是直接複製代碼。

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

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

那麼,如果我們想讓坐騎具有生命值,但是無法攻擊,那該怎麼辦呢?希望你可以看到類的層次結構是如何變得複雜的,即使我們的業務邏輯還是相當簡單。

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

批判性思考

儘管這些設計原則是在數十年經驗中形成的,但盲目地將這些原則應用到代碼之前進行批判性思考是很重要的。

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

作為一個工程師,我們需要根據獨特的情境去批判地評價最好的方法,而不是盲目地遵從並應用任意的原則。

關注點的內聚、耦合和分離

內聚(Cohesion)

內聚代表的是模塊內部責任的分明,或者是模塊的複雜度。

如果我們的類只執行一個任務,而沒有其它明確的目標,那麼這個類就有著高度內聚性。另一方面,如果從某種程度而言它在做的事情並不清楚,或者具有多於一個的目標,那麼它的內聚性就非常低。

我們希望代碼具有較高的內聚性,如果發現它們有非常多的目標,或許我們應該將它們分割出來。

耦合

耦合獲取的是連接不同類的複雜度。我們希望類與其它的類具有儘可能少、儘可能簡單的聯繫,所以我們就可以在未來的事件中交換它們(例如改變網絡框架)。

在很多程式語言中,這都是通過大量使用接口來實現的,它們抽象出處理特定邏輯的類,然後表徵為一種適配層,每個類都可以嵌入其中。

分離關注點

分離關注點(SoC)是這樣一種思想:軟體系統必須被分割為功能上互不重疊的部分。或者說關注點必須分布在不同的地方,其中關注點表示能夠為一個問題提供解決方案。

網頁就是一個很好的例子,它具有三個層(信息層、表示層和行為層),這三個層被分為三個不同的地方(分別是 HTML,CSS,以及 JS)。

如果重新回顧一下我們的 RPG 例子,你會發現它在最開始具有很多關注點(應用 buffs 來計算襲擊傷害、處理資產、裝備條目,以及管理屬性)。我們通過分解將那些關注點分割成更多的內聚類,它們抽象並封裝了它們的細節。我們的 Hero 類現在僅僅作為一個組合對象,它比之前更加簡單。

結語

對小規模的代碼應用這些原則可能看起來很複雜。但是事實上,對於未來想要開發和維護的任何一個軟體項目而言,這些規則都是必須的。在剛開始寫這種代碼會有些成本,但是從長期來看,它會回報以幾倍增長。

這些原則保證我們的系統更加:

可擴展:高內聚使得不用關心不相關的功能就可以更容易地實現新模塊。

可維護:低耦合保證一個模塊的改變通常不會影響其它模塊。高內聚保證一個系統需求的改變只需要更改儘可能少的類。

可重用:高內聚保證一個模塊的功能是完整的,也是被妥善定義的。低耦合使得模塊儘可能少地依賴系統的其它部分,這使得模塊在其它軟體中的重用變得更加容易。

在本文中,我們首先介紹了一些高級對象的類別(實體對象、邊界對象以及控制對象)。然後我們了解了一些構建對象時使用的關鍵原則,比如抽象、泛化、分解和封裝等。最後,我們引入了兩個軟體質量指標(耦合和內聚),然後學習了使用這些原則能夠帶來的好處。

我希望這篇文章提供了一些關於設計原則的概覽,如果我們希望自己能夠在這個領域獲得更多的進步,我們還需要了解更多具體的操作。

原文地址:https://medium.freecodecamp.org/a-short-overview-of-object-oriented-software-design-c7aa0a622c83

本文為機器之心編譯,轉載請聯繫本公眾號獲得授權

✄---

加入機器之心(全職記者/實習生):hr@jiqizhixin.com

投稿或尋求報導:editor@jiqizhixin.com

廣告&商務合作:bd@jiqizhixin.com

相關焦點

  • 面向對象設計七大原則
    開閉原則(Open Close Principle)面向擴展開放,面向修改關閉。7.需要說明的一點是單一職責原則不只是面向對象編程思想所特有的,只要是模塊化的程序設計,都適用單一職責原則。所以:        從大局上看Android中的Paint和Canvas等類都遵守單一職責原則,Paint和Canvas各司其職。
  • 再見了,面向對象的編程
    發生這種情況派生類的作者必須知道基類是如何被實現的。他們必須要知道基類中的每一個變化,因為它可能以無法預料的方法破壞他們的派生類。啊!這個巨大的裂痕會永遠威脅繼承大柱的穩定性。包含和代理又來救火了。通過使用包含和代理,我們從白箱編程到黑箱編程。使用白箱編程,我們需要查看基類的實現。
  • 面向對象編程已死,OOP 永存!
    這種「模式」經常會導致繼承的過度使用,而不會提及過度使用繼承,其實違反了OOP(OOP,Object Oriented Programming,面向對象編程,是一種計算機編程架構)原則。那麼如何避免這種情況呢?本文作者,會給大家介紹下真正的設計指南。
  • SOLID:面向對象設計的五個基本原則
    ();}上面這個貓的接口中存在兩個職責:第一個是管理連接(dial和hangup);第二個是數據傳輸(send和recv)這兩個職責應該被分開Copy不再直接依賴於具體的實現,不管有幾個Reader或Writer的實現,我們都不需要修改Copy。
  • 一步步分析:C語言如何面向對象編程
    這篇文章,我們就來聊聊如何在C語言中利用面向對象的思想來編程。也許你在項目中用不到,但是也強烈建議你看一下,因為我之前在跳槽的時候就兩次被問到這個問題。二、什麼是面向對象編程有這麼一個公式:程序=數據結構+算法。C語言中一般使用面向過程編程,就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步調用,在函數中對數據結構進行處理(執行算法),也就是說數據結構和算法是分開的。
  • 面試對象設計原則(中)
    上期我們講到開閉原則和裡氏替換原則面試對象設計原則(上),這期我們緊接著講面試對象設計原則中的依賴倒置原則
  • DDD 就是把面向對象做好
    充血模型、貧血模型面向對象在處理對象的存儲時,有兩種風格一直爭論不斷。將業務邏輯放到領域對象中,對象不僅需要承載數據也需要承載行為,這種編程邏輯被稱作充血模型。 order.calculate(); }將業務邏輯放到領域對象之外,領域對象只承載數據,以及一些 getter、setter 方法,業務邏輯被另外的類(service)來承載,這種編程模型被稱作貧血模型。
  • JavaScript 的函數式編程與面向對象編程區別在哪?
    本文通過代碼來看一看JavaScript中函數式編程和面向對象編程的差異。
  • 想寫好面向對象的代碼,這幾篇一定要看(中)
    點擊上方「Python編程時光」,選擇「加為星標」第一時間關注Python技術乾貨!在 上一篇文章 裡,我用一個虛擬小項目作為例子,講解了「SOLID」設計原則中的前兩位成員:S(單一職責原則)與 O(開放-關閉原則)。
  • TIA Portal面向對象編程入門
    Programming)被認為是程序設計方法學的一場實質性革命,是程序設計方法學的一個重要裡程碑。儘管時至今日依然有少數人質疑面向對象的編程思想,但我們看到的是面向對象技術發展的越來越好,無論是後端語言(JAVA、C#)或者前端語言(JavaScript、TypeScript),無一不是完全的支持面向對象技術。現在高校的PLC教材基本上採用的還是五六十年前的編程理念,將PLC定位為傳統繼電器控制的替代,以軟元件、寄存器這種古老落後的概念來講授這一門日新月異的現代工業控制編程技術。
  • 史上最全 Python 面向對象編程
    .html面向對象編程和函數式編程(面向過程編程)都是程序設計的方法,不過稍有區別。面向過程編程:1. 導入各種外部庫2. 設計各種全局變量3. 寫一個函數完成某個功能4. 寫一個函數完成某個功能5. 寫一個函數完成某個功能6. 寫一個函數完成某個功能7. 寫一個函數完成某個功能8. .9.
  • 面向對象的Qt編程
    從BOP到OOP基於對象的Qt編程(不推薦)
  • 【編程基礎第五講】java面向對象思想如何理解?
    存在的疑惑:如何理解面向對象的思想?
  • 寫一手漂亮的 JavaScript
    介紹看了很多best practice,卻沒有人教我怎麼去寫一手漂亮的js代碼,今天我來講講我自己寫js的經驗不要在代碼中留大段注釋掉的代碼
  • 【譯】使用UIKit進行面向對象的編程
    Apple甚至建議儘可能的使用協議(protocol)來替換類(class)--這是面向協議編程的關鍵。我讀過許多文章,其中對協議擴展的定義講的很清晰。但都沒有說明面向協議編程真正能為UI開發帶來些什麼。當前可用的一些示例代碼並不是基於一些實際場景的,而且沒用應用任何框架。
  • 整潔架構(Clean Architecture)的Go微服務: 設計原則
    我最近寫了一個 Go 微服務應用程式,這個程序的設計來自三個靈感:清晰架構"Clean Architecture"¹ and SOLID (面向對象設計)² 設計 原則³Spring的容器技術(Spring’s application context)⁴我使用Spring的基於接口的編程和依賴注入
  • C++面向對象程序設計 | 教與學(教學大綱)
    通過本課程的學習,使學生能夠熟悉和掌握C++語言的基本概念、基本語法和編程方法,了解C++語言面向對象的重要特徵,包括類、對象、繼承、多態等內容和相關的定義格式,進而建立面向對象程序設計的思維方式,培養閱讀、分析及設計與編寫運行C++程序的能力,為將來從事基於C++的系統軟體和應用項目開發打下堅實基礎。面向對象方法是本門課程與《軟體工程》的共同內容。
  • 如何寫好C/C++程序
    因此,工程師要恪守"規矩成方圓"的準則,好的工程師要會用工具,要會用"尺"來衡量,這裡的"尺"就是馮·諾伊曼機的內存,做工程時一定要用"尺"量,儘量減小誤差,編寫程序不能違背計算機的基本模型,掌握語言,要做到知其然更知其所以然,利用語言操作計算機硬體,使其有效工作,才是最終目的。
  • 人人都能看懂的JS學習筆記——JS面向對象程序設計
    把自己的小白學習過程總結一下,不要以為我有多偉大啊= =,其實寫總結的過程,也是自己對知識的理解也有進一步的進階。(感覺寫完博客對面向對象編程又有了新的認識)偉大的安德羅妮生前說過:「如果你教會一個6歲小孩編程,你才是真正理解編程。」所以,我想挑戰一下,人人能理解的JS面向對象編程知識。
  • 面向對象聖經
    本文轉載自公眾號 碼農翻身上帝看到人類發明了計算機,但一直在用彙編語言艱難地寫程序,很是傷心,就把編譯器的秘密告訴了約翰·巴科斯,讓他帶領大家寫出了編譯器,從此人類可以用高級語言寫程序,然後編譯成機器語言去運行了。上帝還教會了人類使用順序、循環、分支這三種基本的程序結構來編寫程序。人類很高興,寫的代碼越來越長。