设计模式——UML建模的重要知识类图关系和基本的设计原则小结

引言

UML建模我想很多人可能在学习到的时候都觉得不重要,没有意识到重要性,但是当你拥有一些实际的项目经验和深受维护升级、性能的困扰时候,你会后悔当初为啥没有重视设计模式,没有灵活运用设计模式,而设计模式最本质的思想就是面向对象、面向接口,所以众多模式之间存在着一些相似之处,需要结合具体的业务区分析,到底使用哪种设计模式,而UML建模则可以提供一些依据。

一、UML类图关系概述

UML主要定义了泛化(继承)实现组合聚合关联依赖六种关系,他们之间关系耦合程度依次加强( 泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖)

二、泛化(继承)

泛化一种一般与特殊、一般与具体之间关系的描述,是类与类或者接口与接口之间最常见的关系,具体描述建立在一般描述的基础之上,并对其进行了扩展。通俗说是指的是一个类(称为子类、子接口)继承另外的一个类(称为基类、父接口)的功能,并可以增加它自己的新功能的能力(但父类的非私有属性和方法,子类会无条件继承,这可能导致造成父类的属性和方法在某些子类中不适用的问题,而且继承只能单根继承,无法让一个类拥有多个类的方法和属性),在Java中此类关系通过关键字extends明确标识,UML中用空心三角形箭头的实线连接,箭头指向基类
这里写图片描述

class BaseClass{
public void operate(){}
}
class ConcreateClass extends BaseClass{
@override
public void operate(){}
}

三、实现

实现一种类与接口之间最常见的关系,表示类是接口所有特征和行为的实现,一般通过类实现接口来描述是一个class类实现interface接口(可以是多个)的功能,同时也是Java实现“多继承”的一种机制,,在Java中此类关系通过关键字implements明确标识,UML**用空心三角形箭头的虚线连接,箭头指向接口**
这里写图片描述

interface Interface {
    public void operate();
}

class InterfaceImpl implements Interface {
    public void operate(){}
}

四、依赖(Dependency)

依赖是一种使用的关系即一个类的实现需要另一个类的协助(尽量不使用双向的互相依赖),而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A,在Java中一般表现为类A中的方法需要类B的实例作为其参数或者变量,而类A本身并不需要引用类B的实例作为其成员变量,UML用虚线箭头连接,箭头指向被使用者
这里写图片描述

class Client{
    public void depend(UsedObj obj){

    }
}

class UsedObj{}

五、关联(Association)

关联类与类之间的联接,它使一个类可以访问另一个类的属性和方法,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的且双方的关系一般是平等的、关联可以是单向、双向的(双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。),在Java中被被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量,UML用带普通箭头的实心线连接,指向被拥有者
这里写图片描述

class ConnectObj{
    private AssociatedObj obj;
}

class AssociatedObj {

}

六、聚合(Aggregation)

聚合关联关系的一种特例,他体现的是整体与部分、拥有的关系即has-a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享比如计算机与CPU、公司部门与员工的关系等,表现在代码层面主要体现在成员变量上。UML用带空心菱形的实心线连接,菱形指向整体
这里写图片描述

class Department{
    Employee emp;
}

class Employee{

}

七、组合(Composition)

组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,比如你和你的心脏,表现在代码层面和关联关系是一致的主要体现在成员变量上,只能从语义级别来区分,UML用带实心菱形的实线连接,菱形指向整体
这里写图片描述

class People{
    Heart heart;
}

class Heart{

}

八、软件设计的六大原则

1、单一职责原则

单一职责原则(Single Responsibility Principle,SRP)——就一个类而言,应该仅有一个引起它变化的原因,永远不要让一个类有多个改变的理由,一个类只应该做和一个任务相关的业务,不应该把过多的业务放在一个类中完成,简而言之就是一个类中应该是一组相关性很高的数据、函数封装,这往往是重构的第一步。

2、开闭原则

开闭原则(Open Close Principle,OCP)是Java语言中最基础的设计核心原则,指一个软件系统中的对象(类,模块,方法等) 应该对扩展开放,对修改封闭,开闭的核心就是抽象,将相同的部分封装到一起,使代码重用即闭,把不同的也拿到另一边,便于功能的扩展即开,所以当需求改变的时候我们尽量通过扩展的方式来实现,尽量避免修改原来的代码,因为根据”2-8原则”修改原来的代码可能会引发新的问题,所以要尽量确保原有代码不出问题的话,开闭原则会是一个有效的帮手。

3、迪米特原则

迪米特原则(Low of Demeter,LOD),又叫作最少知识原则(Least Knowledge Principle,“Only tall to your immediately friends”只与最直接的朋友交流,那么映射到编程中就是两个对象具有耦合关系就相当于是朋友)指是一个对象应该对其他对象有最少的了解和相互作用,简而言之就是一个类应该对自己需要耦合或调用类知道得最少,类内部如何实现与使用者无关,只需要知道一个调用方法即可,比如A类要调用B类的a,b,c三个方法,迪米特法则就是只调用方法a,由a去调用b和c,说白了就是尽量让A类少去调用别类的方法

public class Room {
    public float price;

    public Room(float price) {
        this.price = price;
    }
}

class Mediator{
    List<Room> list=new ArrayList<>();
    public Mediator(){
        for(int i=0;i<5;i++){
            list.add(new Room(200*i+200));
        }
    }
    public List<Room> getAllRoms(){
        return list;
    }
}

class Tenant{
    public float price;

    public void rentRoom(Mediator mediator){
        List<Room> rooms=mediator.getAllRoms();
        for (Room room: rooms) {
            if(isSuitable(room)) {
                System.out.println("成功租到房间了" + room);
                break;
            }
        }
    }

    private boolean isSuitable(Room room) {
        if(room.price<200){
            return true;
        }else {
            return false;
        }
    }
}

如上所述是以中介租房为例,作为房客只需要告诉中介房间的价格,中介找到房之后告知就行,但是很明显上面的设计是存在很大问题的:租客Tenant不仅依赖了中介Mediator,还偷偷地和房间Room交互,导致Tenant和Room耦合较高Room变化时会导致Tenant也跟着变化,而Mediator也可能需要变化,耦合逻辑较复杂,因此根据迪米特原则必须解耦,进行以下重构

class Mediator{
    List<Room> list=new ArrayList<>();
    public Mediator(){
        for(int i=0;i<5;i++){
            list.add(new Room(200*i+200));
        }
    }
    public Room rentOut(float price){
        for (Room room: list) {
            if (isSuitable(room)){
                return room;
            }
        }
        return null;
    }

    private boolean isSuitable(Room room) {
        if(room.price<200){
            return true;
        }else {
            return false;
        }
    }
}

class Tenant{
    public float price;
    public void rentRoom(Mediator mediator){
       mediator.rentOut(price);
    }
}

4、依赖倒转法则:

依赖倒转原则(Depenence Inversion Principle,DIP)其实就是只面向接口编程,其实,应该遵循以下三点:

  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象

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

  • 面向抽象编程,不要面向具体编程

所谓抽象就是指接口抽象类细节就是实现类(实现接口和继承抽象类产生的类)。高层模块就是调用端,低层模块就是具体实现类。

5、里氏替换原则

里氏替换原则(Liskov Substitution Principle,LSP)
父类出现的地方,子类一定可以替换,简而言之,所有引用积累的地方必须能透明底使用其子类。不过,尽量使用聚合/组合达到代码复用,少使用继承代码复用

6、接口隔离原则

接口隔离原则(InterfaceSegregation Principles,ISP),客户端不应该依赖他不需要的接口,类间的关系应该建立在最小的接口上。简而言之,使用专门的接口比使用统一的接口要好,不要让用户面对自己用不到的方法

猜你喜欢

转载自blog.csdn.net/crazymo_/article/details/79298256