您的位置:首页 > 编程语言

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

2018-02-05 16:37 926 查看

引言

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),客户端不应该依赖他不需要的接口,类间的关系应该建立在最小的接口上。简而言之,使用专门的接口比使用统一的接口要好,不要让用户面对自己用不到的方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息