Posts 设计模式-设计原则
Post
Cancel

设计模式-设计原则

基本介绍

设计模式可以认为是面向对象编程的编程思想,设计原则即这些编程思想的指导总纲。 SOLID是面向对象设计(OOD)的五大基本原则的首字母缩写:S-单一职责原则;O-开放封闭原则;L-里氏替换原则;I-接口隔离原则;D-依赖倒置原则

SOLID原则

单一职责原则

一个类应该有且只有一个原因引起它的变更 ,通俗地讲:一个类只负责一项功能或一类相似的功能。 如以下设计动物行为的代码,显然不符合单一职责原则,如果需要添加其他场景则需要修改runnning方法,会对之前的逻辑造成隐患:

1
2
3
4
5
6
7
8
9
10
class Animal():
    def __init__(self, name, type):
        self.__name = name
        self.__type = type

    def running(self):
        if self.__type == "水生":
            print(self.__name + "在水里游")
        else:
            print(self.__name + "在陆上跑")

比较好的设计,需要对类进行提取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TerrestrialAnimal():
    def __init__(self, name):
        self.__name = name

    def running(self):
        print(self.__name + "在陆上跑")


class AquaticAnimal():
    def __init__(self, name):
        self.__name = name

    def running(self):
        print(self.__name + "在水里游")

开放封闭原则

OCP(Open Close Priciple),软件实体(如类、模块、函数等)应该对拓展开放,对修改封闭 ,即设计代码的时候需要考虑后续的变化,在增加一个功能的时候,尽可能不修改原始代码,当修改一个模块时,不能影响到其他模块。 如在前文的动物的基础上添加一个观察动物的接口,如下写法不符合OCP原则,因为添加类的时候需要修改display_activity方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Zoo():
    def __init__(self):
        self.__animals = [
            TerrestrialAnimal("狗"),
            AquaticAnimal("鱼")
        ]

    def display_activity(self):
        for animal in self.__animals:
            if isinstance(animal, TerrestrialAnimal):
                animal.running()
            else:
                animal.swimming()

合理的修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BirdAnimal(Animal):
    def __init__(self, name):
        super().__init__(name)

    def moving(self):
        # 需要定义一致的接口
        print(self.__name + "在天上飞")


class Zoo():
    def __init__(self):
        self.__animals = []

    def add_animal(self, animal):
        self.__animals.append(animal)

    def display_activity(self):
        for animal in self.__animals:
            animal.moving()

里氏替换原则

LSP(Liskou Substitution Principle),所有能引用基类的地方必须能透明地使用子类 ,即父类能出现的地方子类都能替换实现相同的功能,反之则不成立。 如下,建立陆上动物的子类:猴子,猴子类能替换陆生动物类,但是陆生动物类无法替换猴子类功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Monkey(TerrestrialAnimal):
    def __init__(self, name):
        super().__init__(name)

    def climbing(self):
        print(self._name + '在爬树,动作灵活轻盈')

class Zoo():
    def __init__(self):
        self.__animals = []

    def add_animal(self, animal):
        self.__animals.append(animal)

    def display_activity(self):
        for animal in self.__animals:
            animal.moving()

    def mokey_climbing(self, monkey):
        # 子类特有的功能
        monkey.climbing()

依赖倒置原则

DIP(Dependence Inversion Principle),高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。 ,即需要将具有相同/相似功能的类,抽象成接口或者抽象类,让具体的实现类继承这个抽象类。 如需要给前文的动物添加一个吃东西的接口,简单的设计如下,存在的问题是:食物没有和动物隔离,导致添加食物类型等场景时修改代码变得困难。

1
2
3
4
5
6
7
8
class Dog():
    def eat(self, meat):
        pass


class Fish():
    def eat(self, grass):
        pass

比较好的设计是:创建动物和食物的抽象类,动物进食实现应该依赖抽象食物类,而不是具体的食物实例。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from abc import ABCMeta, abstractmethod


class Animal(metaclass=ABCMeta):
    def __init__(self, name):
        self._name = name

    def eat(self, food):
        if self.check_food(food):
            print(self._name + "eat" + food.get_name())
        else:
            print(self._name + "doesn't eat" + food.get_name())

    @abstractmethod
    def check_food(self, food):
        """检查食物是否能吃"""
        pass


class Food(metaclass=ABCMeta):
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    @abstractmethod
    def category(self):
        """食物类别"""
        pass


class Dog(Animal):
    def __init__(self):
        super().__init__("Dog")

    def check_food(self, food):
        return food.category() == "Meat"

接口隔离原则

ISP(Interface Segregation Principle),客户端不应该依赖它不需要的接口。用多个细粒度的接口来替代多个方法组成的复杂接口,每个接口服务于一个子模块。 ,即尽可能细化接口,接口方法尽可能小(适度)。 如对动物的行为描述设计接口,动物本身具备比较多的特性,需要对这些特征进行细粒度切分,如:奔跑,游泳,飞行等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from abc import ABCMeta, abstractmethod


class Animal(metaclass=ABCMeta):
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    @abstractmethod
    def feature(self):
        pass

    @abstractmethod
    def moving(self):
        pass


class IRunnable(metaclass=ABCMeta):
    """奔跑接口"""
    @abstractmethod
    def running(self):
        pass


class IFlyable(metaclass=ABCMeta):
    """飞行接口"""
    @abstractmethod
    def flying(self):
        pass


class INataable(metaclass=ABCMeta):
    """游泳接口"""
    @abstractmethod
    def swimming(self):
        pass


class MammalAnimal(Animal, IRunable):
    """哺乳动物"""
    def __init__(self, name):
        super().__init__(name)

    def feature(self):
        print(self._name + "的生理特征:恒温,胎生,哺乳。")

    def running(self):
        print("在陆上跑")

    def moving(self):
        print(self._name + "的活动方式:", end="")
        self.running()


class Bat(MammalAnimal, IFlyable):
    """蝙蝠:哺乳动物,但是不会走,会飞"""
    def __init__(self, name):
        super().__init__(name)

    def running(self):
        print("行走功能已经退化")

    def flying(self):
        print("在天空飞...", end="")

    def moving(self):
        print(self._name, "的活动方式:", end="")
        self.flying()
        self.runing()

其他原则

LoD原则

每一个逻辑单元应该对其他逻辑单元有最少的了解;也就是说只亲近当前的对象。只和直接亲近的对象说话,“不和陌生人说话” ,该原则也被称为迪米特原则。 典型场景:类A中有类B的对象,类B中又有C的对象。现在A需要访问C的属性,写法如下:

1
2
3
4
# 不合理写法
a.get_B().get_C().get_properties()
# 合理写法
a.get_C_properties()

KISS原则

Keep It Simple and Stupid ,代码设计应该尽可能解耦,不要过度设计,让代码简单易懂。

DRY原则

Don't reapeat yourself ,多次遇到相同问题,需要提取共同的解决方法,提高代码复用率,实现方式:

  • 函数级别的封装
  • 类级别的抽象
  • 泛型设计

YAGBI原则

You aren't gonna need it, don't implement something until it is neccessary. ,只考虑和设计必需的功能,避免过度设计。

Rule Of Three原则

三次原则:当一个功能第三次出现的时候,在进行抽象化。即第一次实现,大胆去做;第二次实现,虽然反感,但是还是去做;第三次实现,则要去审视将重复/相似的代码进行抽象,包装为通用接口。 目的:省事,循序渐进发现模式,防止过度冗余

CQS原则

Command-Query Separation ,查询(Query)即返回一个值来回应某个问题,命令(Command)即一个方法改变了对象的状态。两者需要尽可能分离,有利于提高系统的性能,也有利于增强系统的安全性。

This post is licensed under CC BY 4.0 by the author.

设计模式-MVC模式

Bert:过去-现在-未来