软件体系结构——第七章<面向对象的设计原则>

一、设计需要原则

面向对象的设计原则是构造高质量软件的出发点

什么是好的设计?

  • 容易理解

  • 容易修改和扩展

  • 容易复用

  • 容易实现与应用

  • 简单、紧凑、经济适用

  • 让人工作起来心情愉快的设计

面向对象的基本设计原则(模式):

  • LSP:Liskov替换原则 The Liskov Substitution Principle

  • OCP:开放-封闭原则 The Open-Close Principle

  • SRP:单一职责原则 The Single Responsibility Principle

  • ISP:接口隔离原则 The Interface Segregation Principle

  • DIP:依赖倒置原则 The Dependency Inversion Principle

二、LSP-Liskov替换原则

LSP(The Liskov Substitution Principle)

定义:“若对于类S的任一对象o1,均有类T的对象o2存在,使得在类T定义的所有程序P中,用o1替换o2之后,程序的行为不变,则类S是类T的子类”,即“子类对象必须可以替换父类对象”

违背LSP原则的范例——

image.png

怎么解决此问题?——使用抽象

image.png

抽象类与具体类的区别:

image.png

正确方案:

image.png

三、OCP-开放-封闭原则

lOCP(The Open-Close Principle)的定义

  • 对任何一个事物来说,“变化是永恒的主题,不变是相对的定义”,由此,可得出软件实体(类、模块、函数等)应该是可扩展的,但是不可修改的——功能不变性

OCP原则的特征:

  • 对于扩展是开放的(Open for extension):模块的行为可以扩展,当应用的需求改变时,可以对模块进行扩展,以满足新的需求

  • 对于更改是封闭的(Closed for modification):对模块行为扩展时,不需改动模块源代码即可满足新需求

OCP的关键在于扩展抽象的利用:

  • 抽象技术:abstract class、Interface

  • 抽象内容预见了可能的所有扩展(闭)

  • 由抽象可以随时导出新的类(开)

实例:手与门

手与门的行为表现为:

  • 开门(open)

  • 关门(close)

  • 判断门的状态(isOpened)

image.png

public class Door {
    private boolean _isOpen=false;
    public boolean isOpen(){
        return _isOpen;
    }
    public void open(){
        _isOpen = true;
    }
    public void close(){
        _isOpen = false;
    }
}

public class Hand {
    public Door door;
    void do() {
        if (door.isOpen())
             door.close();
        else
            door.open();
    }
}

//Test
public class SmartTest {
    public static void main(String[] args) {
        Hand myHand = new Hand();
        myHand.door = new Door();
        myHand.do();
    }
}

新的需求出现后如何应对?如:用手开关抽屉

image.png

public class Hand {
    public Door door;
    public Drawer drawer;
    void do(int item) {
        switch (item){
            case 1:  //开门程序
                if (door.isOpen())
                     door.close();
                else   door.open();
                break;
	case 2: //开抽屉程序
 	    if (drawer.isOpen())
                      drawer.close();
                 else  drawer.open();
           break; 
        }
    }
}

//Test
public class SmartTest {
    public static void main(String[] args) {
        Hand myHand = new Hand();
        myHand.door = new Door();        
        myHand.drawer = new Drawer();
        myHand.do(1);   //手开门
    }
}

发现,这样改动非常麻烦!

我们设计出符合OCP的设计方案:

image.png

public interface Excutable {
    public boolean isOpen();
    public void open();
    public void close();
}

public class Hand {
    public Excutable item;
    void do() {
        if (item.isOpen())
            item.close();
        else
            item.open();
    }
}

public class Door implements Excutable {
    private boolean _isOpen = false;
    public boolean isOpen() {
        return _isOpen;
    }
    public void open() {
        _isOpen = true;
    }
    public void close() {
        _isOpen = false;
    }
}

public class Drawer implements Excutable {
    private boolean _isOpen = false;
    public boolean isOpen() {
        return _isOpen;
    }
    public void open() {
        _isOpen = true;
    }
    public void close() {
        _isOpen = false;
    }
}

//add a refrigerator
public class Refrigerator implements Excutable {
    private boolean _isOpen = false;
    public boolean isOpen() {
        return _isOpen;
    }
    public void open() {
        _isOpen = true;
    }
    public void close() {
        _isOpen = false;
    }
}

//Test
public class SmartTest {
    public static void main(String[] args) {
        Hand myHand = new Hand();
        myHand.item = new Door();
        myHand.do();
    }
}

通过扩展应对新需求,不需要修改任何原有的设计和代码~

OCP的优势:

  • 可以极大提高软件的设计质量,耦合度低、易扩展、易修改

  • 当预测到可能的变化时,通过抽象类(接口)来隔离它。

  • 关键技术是“扩展抽象”原则的利用。

  • 成功的预测将极大提高软件的生存力,而失败的预测,将带来“不必要的复杂性”的设计“臭味”

四、SRP-单一职责原则

作为面向对象系统最基本的元素,类自身的设计质量将直接影响到整个设计方案的质量。因此,对于单个类而言,最核心的工作就是类的职责分配过程——职责单一。

SRP(The Single Responsibility Principle)定义:就一个类而言,应该仅有一个引起它变化的原因,即内聚性——一个类只完成一个功能

违反SRP的案例:

image.png

解决方案:

image.png

SRP是一个非常简单的原则,但却是最难正确应用的原则之一——类职责的高内聚

  • 如模块独立性原则“高内聚、低耦合”,知道该原则是追求的设计目标,但难以描述怎样才能达到高内聚的目标

SRP明确告诉设计人员必须保持类职责的内聚性

  • 主要做法:结合业务场景考虑职责的相关性,与系统的耦合程度密切关联起来

五、ISP-接口隔离原则

ISP(The Interface Segregation Principle)定义

  • 客户不应该依赖他们用不到的方法,只给每个客户提供所需的接口——角色

  • 为了避免“肥接口(没有关系的接口合并在一起)(fat interface)”,应当以一个类实现多个专用的接口,而各客户仅仅获知必须的接口

优势:

  • 为不同角色提供宽窄不一的接口,以对付不同的客户端或使用者(都是业务参与者);

  • 接口的职责明确,有利于系统的维护,同时,有利于降低设计成本;

  • 平常设计时总是向客户端和使用者提供public接口是一种承诺,应尽量减少这种承诺;

  • 接口污染:为节省接口的数目,将类似的接口合并,使得接口变得臃肿,造成接口污染。

接口污染案例:

image.png

解决方案:

  • 使用委托分离接口: 适配器(Adapter)模式

  • 使用多重继承(实现)分离接口

image.png

六、DIP-依赖倒置原则

传统的层次结构:

image.png

DIP特性:

  • 高层模块不依赖于低层模块,二者都依赖于抽象(需要定义中间抽象层)

  • 抽象不依赖于细节,细节依赖于抽象

  • 针对接口编程,不要针对实现(细节)编程

符合DIP的系统:

image.png

启发式原则:

  • “依赖于抽象”—程序中所有依赖关系都应该终止于抽象类或者接口

  • 启发式原则——要符合LSP原则

    • 任何类都不应该从具体类派生(始于抽象、来自具体)
    • 任何方法都不应该改写其任何基类中已经实现的方法(除非有特殊的需求)
  • UML中箭头的方向就代表了依赖的方向

本质:

  • 通过抽象提取业务本质,并建立一个稳定的静态结构(抽象类图)描述这个业务本质

  • 对于具体的业务规则的处理是在这个业务本质的基础上的扩展

  • 注意:技术、工具、意识形态等的发展可能使业务规则不断变化,但业务的本质不变;因此,DIP原则可帮助设计人员轻松的适应这些变更

posted @ 2022-05-03 20:40  我在吃大西瓜呢  阅读(178)  评论(0编辑  收藏  举报