设计模式--面向对象设计原则、UML
来源:互联网 发布:yy网络怎么创建直播间 编辑:程序博客网 时间:2024/05/16 08:01
基础
1、一些常用的UML标记
- 用 实线加> 来表示某个类中包含另一个类的实例的意思
- 用 实线加△ 来表示某个类继承了另一个类的意思
- 用 虚线加△ 来表示某个类实现了某个接口的意思
1.1 实体类的表示
上图就是UML图中实体类的表示方法,类图分成三层:第一层是类名,如果类是抽象的,就用斜体表示;第二层是字段和属性;第三层是操作方法。如果方法或者属性是public的就在前面加’+’,private的加’-‘,procted的加’#’。
1.2 接口的表示
接口的表示和类基本相同,只是它的类名上面加了<<interface>>
,而且没有字段和属性一层。
1.3 继承以及实现接口
继承基类或者实现接口的表示方式已经在上图中裂了出来,无需额外的说明。
1.4 聚合
上面的图表示的是两个类之间的聚合关系,它通常用来表示一个类中包含许多个另一个类的实例,通常是指一个类中包含另一个类的数组或者容器。比如,在上图中表示的就是在雁群中可以有很多个大雁,这里的雁群和大雁之间就属于聚合关系。(可以理解成一个类中聚集了很多个另一个类的实例)
1.5 组合
上面表示的就是组合关系。所谓的组合,就是在一个类中包含另一个类的实例,而另一个类是该类的一部分。它和聚合的区别是,聚合中的一个类不是另一个类的一部分(是在另一个类的容器或者数组中)。
在组合中可以在下方标注数字来标识组合的数量关系,比如一个鸟有两个翅膀的话,就在线下面分别用数字1和2来表示。
1.6 依赖
依赖关系与聚合和组合的不同之处在于,依赖关系是指在方法中要依赖其他的类作为参数进行输入。
2、面向对象设计六大原则
2.1 单一责任原则
就一个类而言,应该只有一个引起它发生变化的原因。
比如,我们经常在界面程序中增加各种逻辑,这样的代码在修改起来非常困难,复用不可能,也缺乏灵活性。如果一个类承担的责任过多,就等于把很多责任耦合在一起,一个责任的变化可能会影响或者削弱这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化的时候,设计会遭到意想不到的破坏。
2.2 开放-封闭原则
软件实体(类、模块、函数等)应该对拓展开放,对修改关闭。淡然,绝对的封闭是不可能的,但是我们在设计的时候,应该猜出最有可能发生变化的部分,然后构造抽象来隔离变化。所以,当有了新需求的时候,我们的代码应该是通过添加新的代码来进行的,而不是修改现有的代码。
典型的例子是策略模式,以及策略+工厂,将两者结合,我们就只需要修改工厂和增加新的策略,来为程序添加新的功能。
2.3 依赖倒转原则
依赖倒转有两层意思:
- 高层模块不应该依赖底层模块,两个都应该依赖抽象;
- 抽象不应该依赖细节,而细节应该依赖抽象。
所谓的“高层模块不应该依赖底层模块”,就是说,我们在写代码的时候,不应该在一个类中通过库函数的形式调用另一类的方法。常见的方式是,将一些操作封装成一个Helper方法,然后在Service层中调用这些方法。
所谓的依赖于抽象应该是指面向接口编程或者是抽象类,也就是只声明接口类型,然后将接口的具体是实现赋值给它。这样做的好处是,假如某个接口的实现由问题,那么我们只需要修改这个接口的实现,但是接口前后的逻辑是不需要发生变化的。
2.4 里氏替代原则
子类型必须能够替换它们的父类型
如果软件实体使用的是父类,那么一定适用于其子类。也就是把父类都替换成子类,程序的行为不会发生变化。比如,我们在程序中的所有位置都使用的是接口,如果我们要将接口替换成该接口的子类,那么程序的行为不会发生变化。
里氏代换原则的4层含义
1.子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
里氏代换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏代换原则就是表达了这一层含义。
2.子类中可以增加自己特有的方法
3.当子类的方法实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
2.5 迪米特法则
迪米特法则:也称为最少知识原则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的public方法,我就调用这么多,其他的一概不关心。
下面的例子来自csdn博客
// 类A和类B是好朋友,能找类B来帮忙public class A{ private String name; public A(String name) { this.name = name; } public B getB(String name) { return new B(name); } public void work() { B b = getB("李四"); C c = b.getC("王五"); c.work(); }}// 类B和类C是好朋友,能知道类C能办成此事public class B { private String name; public B(String name){ this.name = name; } public C getC(String name) { return new C(name); }}// 类C能够办成此事public classC { public String name; public C(String name) { this.name = name; } public void work() { System.out.println(name + "把这件事做好了"); }}// 客户端public classClient { public static void main(String[] args) { A a = new A("张三"); a.work(); }}
上面的程序最终的输出结果是:
王五把这件事情做好了
但是因为在A中调用了C来解决这个问题,而C和A本身是没有联系的。这种场景在实际开发中是非常常见的一种情况。对象A需要调用对象B的方法,对象B有需要调用对象C的方法……就是常见的getXXX().getXXX().getXXX()。所以,如果在一个类中通过另一个类的getter方法获取了其他的实例时,就应该注意,可能导致代码之间的耦合过深了。
下面是使用迪米特法则之后的结果:
// 类A和类B是好朋友,能找类B来帮忙public class A { public String name; public A(String name) { this.name = name; } public B getB(String name) { return new B(name); } public void work() { B b = getB("李四"); b.work(); }}// 类B和类C是好朋友,能知道类C能办成此事public class B { private String name; public B(String name) { this.name = name; } public C getC(String name) { return new C(name); } public void work() { C c = getC("王五"); c.work(); }}// 类C能够办成此事public classC { public String name; public C(String name) { this.name = name; } public void work() { System.out.println(name + "把这件事做好了"); }}// 客户端public classClient { public static void main(String[] args) { A a = new A("张三"); a.work(); }}
上修改的代码中,我们在B和A中分别只获取它们的关联的类来处理。虽然最终的输出结果是一样的,但是这种方式不会像上面那样耦合过深,便于对代码进行修改。
关于应用迪米特法则的注意事项:
- 在类的划分上,应该创建有弱耦合的类;
- 在类的结构设计上,每一个类都应当尽量降低成员的访问权限;
- 在类的设计上,只要有可能,一个类应当设计成不变类;
- 在对其他类的引用上,一个对象对其它对象的引用应当降到最低;
- 尽量降低类的访问权限;
- 谨慎使用序列化功能;
- 不要暴露类成员,而应该提供相应的访问器(属性)。
2.6 合成/聚合原则
合成/聚合原则,尽量使用合成/聚合,尽量不要使用类继承。
在面向对象设计中,如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类。如果基类的实现发生改变,则子类的实现也不得不发生改变。从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性。于是就提出了合成/聚合复用原则,也就是在实际开发设计中,尽量使用合成/聚合,不要使用类继承
继承复用与合成/聚合复用
1.继承复用
继承复用通过扩展一个已有对象的实现来得到新的功能,基类明显地捕获共同的属性和方法,而子类通过增加新的属性和方法来扩展父类的实现。继承是类型的复用。
继承复用的优点:
- 新的实现较为容易,因为超类的大部分功能可通过继承关系自动进入子类;
- 修改或扩展继承而来的实现较为容易。
继承复用的缺点:
- 继承复用破坏封装,因为继承将细节暴露给子类;
- 如果父类的实现发生改变,那么子子类的实现也不得不发生改变;
- 从父类继承而来的实现是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。
2.合成/聚合复用
由于合成/聚合可以将已有对象纳入到新对象中,使之成为新对象的一部分,所以新对象可以调用已有对象的功能
合成/聚合复用的优点:
- 该复用支持封装;
- 该复用所需的依赖较少;
- 每个新的任务可将焦点集中在一个任务上。
合成/聚合复用的缺点:
- 通过这种复用建造的系统会有较多的对象需要管理;
- 为了能将多个不同的对象作为组合块来使用,必须仔细地对接口进行定义。
3、针对接口编程
所谓针对接口编程就是,假如有一个Animal接口,而Dog和Cat实现了它,那么当我们想要创建一个Dog的实例的时候,我们这样定义:
Animal dog = new Dog();
而不是
Dog dog = new Dog();
这样的好处是,如果你想要修改dog的实现,将其替换成一个cat,那么你可以使用setter方法注入。也就是说,当我们使用接口的时候,我们可以有更多的选择的余地。因为只要是Animal的实现,我们都可以将其赋值给dog.
4、内聚
内聚用来都连一个类或模块紧密地达到单一目的或责任。当一个模块或一个类被设计成只支持单一相关的功能时,我们说它具有高内聚;反之,被设计成一组不想干的功能时,我们称它具有低内聚。
更多内容
1、该项目整理了设计模式、Java语法、JVM、SQL、数据结构与算法等相关内容:https://github.com/Shouheng88/Java-Programming。
2、由于时间仓促,不免于存在错误,欢迎批评指正。
- 设计模式--面向对象设计原则、UML
- 面向对象设计模式原则
- 面向对象设计模式原则
- 【设计模式】面向对象原则
- UML中GRASP面向对象设计原则
- UML面向对象的设计原则
- 面向对象设计原则和UML表示
- 面向对象设计原则及设计模式
- 【设计模式】 面向对象六大设计原则
- 面向对象设计模式Solid设计原则
- [设计模式]面向对象设计原则之单一职责原则
- [设计模式]面向对象设计原则之里氏替换原则
- [设计模式]面向对象设计原则之依赖倒置原则
- [设计模式]面向对象设计原则之接口隔离原则
- 设计模式系列-面向对象葵花宝典-UML
- 面向对象设计模式与原则
- 面向对象的设计模式原则
- 面向对象设计模式与原则
- LeeCode 451. Sort Characters By Frequency
- Mahmoud and a Message CodeForces
- 数据库的四种隔离级别
- 【LeetCode算法练习(C++)】Search in Rotated Sorted Array
- Shell环境和变量生存期
- 设计模式--面向对象设计原则、UML
- 决策树算法以及熵、条件熵、信息增益等整理
- code forces 894C [分割构造gcd]
- [OpenGL] 初识GLFW
- 为ActiveMQ服务器设置安全验证
- Celery-4.1 用户指南: Routing Tasks
- ubuntu 安装redis两种方式 教程
- Python3实现银行家算法、安全性算法
- 如何根据一个式子判断是什么进制