Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

面向对象的设计原则

16
Jul
2006

面向对象的设计原则

By Alex
/ in Architecture
/ tags 设计模式
0 Comments
简化原则

在满足非功能需求的前提下,尽量保持设计的简单。简单的设计有利于其它开发人员理解。

开放-关闭原则(Open Closed Principle,OCP)

类应该对扩展开放,对修改关闭。

为了遵循开闭原则,通常需要引入新的抽象层次,从而增加代码的复杂度。因此,没必要在任何时候严格遵守开闭原则,应当只去考虑那些最有可能变化的代码。

单一职责原则(Single Responsibility Principle,SRP)

一个类只应当有一个引起变化的原因。该原则要求我们尽量让每个类保持单一责任。

以集合和迭代器为例,集合类本身的职责是管理一群对象,如果不引入迭代器,直接让集合类实现迭代功能,那么集合类就有了两项职责,相应的就是两项变化的原因。

当一个模块或者类被设计为只支持一组相关功能时,我们称之为高内聚(Cohesion),反之则称为低内聚,低内聚往往意味着不必要的耦合。

下面是一个小型射击游戏的例子,一个类中牵涉到三类不同的职责:游戏会话、游戏动作、玩家信息,这是一个典型的低内聚高耦合的例子:

Python
1
2
3
4
5
6
7
8
# 一个射击游戏
class Game:
    def login( self ): pass  # 登录
    def signup( self ): pass  # 注册
    def move( self ): pass  # 移动
    def fire( self ): pass  # 开火
    def getName( self ): pass  # 获取用户名
    def getHighScore( self ): pass  # 获取高分
分离变化原则

软件开发中,唯一不变的是变化这个事实。

软件设计要解决的一个核心问题,就是将软件中变化与不变的部分进行分离。尽可能的避免将易变化的代码和不需要变化的代码混在一起,这样做至少有以下好处:

  1. 对于易变的代码:后续对其进行修改,不至于影响到软件中已经稳定的部分
  2. 对于不变的代码:有利于提高其可重用性

考虑以下场景,我们需要设计一些人员类,这些人员可以打招呼,最原始的做法是使用继承:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding: UTF-8 -*-
from abc import abstractmethod
 
 
class Person( object ):
    @abstractmethod
    def sayHello( self ):
        pass
    
class Chinese( Person ):
    def sayHello( self ):
        print "你好"  # 一般中国人这么说
class English ( Person ):
    def sayHello( self ):
        print "hi"  # 一般外国人这么说

 可以看到,中国人和英国人使用不同的方式打招呼。在后续开发过程中,我们发现哑巴是不会说话的,因此继续扩展类层次:

Python
1
2
3
class Dumb( Person ):
    def sayHello( self ):
        pass  # 哑巴不说话

很快我们就发现,中国人和英国人都可以是哑巴,难道还要继续继承下去?就像这样:

Python
1
2
class ChineseDumb( Dumb , Chinese ):
    pass

仔细思考一下,可以发现打招呼的方式和人员类型耦合在一起,不同人员类型必须以固定的方式来打招呼,是这样么?中国人也可以学英语,英国人也可以是哑巴……既然打招呼的方式本质上和人的类型无关、并且打招呼的方式可以随着人员的具体实例灵活变化,我们就应该将这种变化隔离,变继承为组合:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: UTF-8 -*-
from abc import abstractmethod
 
class GreetHehavior( object ):
    @abstractmethod
    def greet ( self ):
        pass
 
class ChineseGreetHehavior( GreetHehavior ):
    def greet( self ):
        print "你好"
 
class Person( object ):
    def sayHello( self ):
        self.greetHehavior.greet()
 
class English ( Person ):
    pass
 
if __name__ == '__main__':
    p = English()
    p.greetHehavior = ChineseGreetHehavior()
    p.sayHello() #英国人也可以用中文打招呼,只要他学习了汉语

现在,易变的打招呼逻辑被封装在GreetHehavior接口中,并且可以灵活的与人员的实例进行组合。

类的两大要素是状态和方法(行为),而例子中重构后的代码却把两要素之一:行为给独立为新的类型,这其实是合理的,作为新类型的“行为”也可以有自己的变量,例如打招呼时说话的速度。

针对接口编程

针对接口编程,而不是针对实现编程。这里的接口是泛指的超类型概念,在不同的编程语言里面有不同的具体表象,可以是抽象类、或者类似Java语言中的interface。

多态——在运行时动态绑定,并寻找真正的行为(代码),是针对接口编程的技术前提。

具体的说,针对接口编程,要求变量以超类型的形式进行声明,当然这对于某些动态语言不适用,因为其语法结构中就不要求声明变量类型。

C++
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
#include <iostream>
using namespace std;
class Person
{
    public:
        virtual ~Person()
        {
        }
        //虚函数用于启用动态绑定
        virtual void sayHello()=0;
};
class English : public Person
{
    public:
        virtual ~English()
        {
        }
        void sayHello()
        {
            cout << "Hi" << endl;
        }
};
class Chinese : public Person
{
    public:
        virtual ~Chinese()
        {
        }
        void sayHello();
};
void Chinese::sayHello()
{
    cout << "你好" << endl;
}
int main( int argc, char **argv )
{
    Person* p = new Chinese; //面向接口的变量声明
    //虽然声明类型是Person,但是多态使程序调用了子类的代码
    p->sayHello(); //你好
    delete p;
    p = new English;
    p->sayHello(); // Hi
    Chinese alex;  //面向实现的声明
    return 0;
}
更多的使用组合而不是继承

使用组合建立类系统,具有很大的弹性。利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。使用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。

通过动态的组合对象,可以写新的代码添加新功能,而无须修改现有代码,因而引入Bug或者意外副作用的可能性就会大幅度减小。这也体现了开闭原则。

尽量让交互对象松耦合

松耦合的设计有助于建立有弹性的OO系统,能够应对变化,因为对象之间的互相依赖降到了最低。

当两个对象之间松耦合时,它们依然可以交互,但是不太清楚彼此的细节。例如在观察者模式中,主题、观察者就是一种松耦合关系,因为:

  1. 关于观察者的一切,主题只知道它实现了观察者接口,其它一概不知
  2. 在任何时候,主题的观察者可以被动态的添加、删除
  3. 当新类型的观察者出现时,主题的代码不需要进行修改
  4. 修改主题或者观察者的任一方,对方不会受到影响
依赖倒转原则(Dependency Inversion Principle)

要依赖于抽象,而不是依赖于具体类。但凡在类A的代码中出现类B的引用,就意味着类A依赖于类B。

该原则乍看与面向接口编程类似,但是更加强调抽象——在类层次中,处于高层的组件不应该依赖于处于低层的组件,同时不管高低层组件,两者都应该依赖于抽象。以下几个TIPS可以帮助你遵循此原则:

  1. 变量不要持有具体类的引用——避免new,使用工厂规避
  2. 不要让类派生自具体类——派生是一种强依赖,应当派生自抽象类或者接口
  3. 不要覆盖基类中已经实现的方法——如果覆盖基类已经实现的方法,说明基类并不是一个真正适合被继承的抽象,基类中实现的方法应该被所有子类共享

在Java中,使用基于Annotation,比起XML文件的配置方式,有时会引入对DIP的违反,例如Hibernate的多态映射:

Java
1
2
3
4
5
6
7
8
9
10
11
12
@AnyMetaDef ( name = "mapItemTarget",
        idType = "string",
        metaType = "integer",
        metaValues = {
        @MetaValue ( value = "0", targetEntity = AbstractMonitorPoint.class ),
        @MetaValue ( value = "1", targetEntity = AbstractDeviceHost.class ),
        @MetaValue ( value = "2", targetEntity = AbstractGeneralDevice.class ),
        @MetaValue ( value = "3", targetEntity = Area.class ),
        } )
@Any ( metaDef = "mapItemTarget", metaColumn = @Column ( name = "TGT_TYPE") )
@JoinColumn ( name = "TGT_ID")
private AbstractEntity        target;
迪米特法则(Law of Demeter)

又称最少知识原则(Least Knowledge),该原则告诫我们“不要和陌生人说话”——对象之间的交互应当尽量减少,仅仅和联系最紧密的对象进行交互。

太多的类进行网状交互会导致难以维护的耦合,复杂的交互导致代码难以被理解。如何能在实践中遵循该原则呢?

在一个对象的方法体内,我们应该只调用以下范围内的方法:

  1. 该方法体中创建的对象的方法
  2. 当前对象本身的方法
  3. 被作为方法参数传入的对象的方法
  4. 当前对象任何属性(组件,Has-A)的方法

按照上述规则,我们不应该调用作为返回值的对象的任何方法,例如:

Python
1
2
3
def getTemp(self):
    #调用了监控站的温度计的方法
    return self.station.getThermometer().getTemperature()

应当改为: 

Python
1
2
3
def getTemp(self):
    #调用了监控站的温度方法,至于监控站如何得到温度,我不关心
    return self.station.getTemperature()

该原则会造成一些问题:

  1. 导致更多的包装类/方法被制造出来,增加复杂度和降低性能 
好莱坞原则

别调用(打电话给)我们,我们会调用(打电话给)你。好莱坞原则可以防止“依赖腐败”——当高层组件依赖于低层组件,低层组件又依赖于高层组件,高层组件又依赖于边侧组件,边侧组件又依赖于低层组件时,依赖腐败就产生了(环状依赖),这样的情况下没人能轻易搞清楚系统是如何设计的。

在好莱坞原则下,低层组件可以将自己挂钩到系统上,但是高层组件会决定什么时候、如何使用这些低层组件。

好莱坞原则是创建框架时常见的一种技巧。

权衡设计原则

不能生硬的硬搬上面的设计原则,在实际的设计中,可能需要做很多折衷。例如组合模式就在透明性和安全性之间做了取舍,为了透明性,它在超类接口中定义了不适用于子类的方法,并且违反了SRP。

合理使用设计模式

所谓模式,是指在某种情境(Context)下,针对某问题的一种解决方案。情境必须是重复的、不断出现的;解决方案必须是通用的,用来解决约束,达到目标。

使用模式切忌生搬硬套,为了使用模式而使用模式,可以根据实际需要对模式进行变形。模式可能带来不必要的复杂性,因此应当在权衡后使用。

设计模式可以分为:

  1. 创建型:牵涉到如何进行对象的实例化,提供一种方法将Client从需要实例化的对象中解耦。这类模式包括单例、建造者、原型、抽象工厂、工厂方法等
  2. 行为型:牵涉到类与对象如何交互和分配职责。这类模式包括模板方法、访问者、中介者、迭代器、备忘录、命令、观察者、职责链、解释器、状态、策略等
  3. 结构型:牵涉到如何把类或对象组合到更大的结构中。这类模式包括:迭代器、代理、外观、组合、轻量、适配器、桥接等

另外一种分类方式:

  1. 类模式:描述类之间的关系如何通过继承定义,类模式的关系是在编译期间建立的。这类模式包括:模板方法、工厂方法、适配器、解释器等
  2. 对象模式:描述对象之间的关系,主要利用组合定义,对象模式的关系通常在运行时建立,更加动态有弹性。这类模式包括:组合、装饰器、代理、策略、桥接、轻量、抽象工厂、单例、原型、状态、建造者、命令、外观、访问者、职责链、备忘录、中介者、观察者、迭代器等

 

← C语言学习笔记
汪震在徐州 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 职责链模式
  • 不变模式
  • 工厂模式
  • 服务定位器模式
  • 迭代器模式

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2