设计模式的目的
解耦合,高内聚,提高程序的可维护性,扩展性,复用性,灵活性
23种设计模式 遵从的原则就是 7大设计原则
7大设计原则
- 单一职责原则
- 接口隔离原则
- 依赖倒置原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
单一职责原则
一个模块或一个类,甚至一个方法只应该负责一个功能
- 降低类的复杂度
- 提高类的可读性和可维护性
- 降低代码变动对类的影响产生的风险
- 通常应当遵从单一职责原则,只有类中方法很少,逻辑足够简单时,我们可以降低到在类的方法层面上去实现单一职责原则
单一职责原则可以在类的层面上实现,也可以通过方法的层面上实现
接口隔离原则
- 客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上
错误示例(上)和正确示例(下)
依赖倒置原则
- 高层模块不应该依赖于底层模块,二者都应该依赖于抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
依赖倒置原则的中心思想是面向接口编程
错误示例
class Email{
public String getInfo(){
return "Email Message";
}
}
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
}
分析:
该方法较为简单;
但是如果需要获取的信息对象是微信或者QQ,则需要新增类并且还需要修改Person相应的接收方法
改善后的代码:
//新增的接口
interface IReceiver{
public String getInfo();
}
class Email implements IReceiver{
public String getInfo(){
return "Email Message";
}
}
//新增的微信消息类也只需要实现IReceiver接口即可
class Wechat implements IReceiver{
public String getInfo(){
return "Wechat Message";
}
}
//接收消息类只需要依赖接口,而不需要在增加类时进行方法的修改
class Person{
public void receive(IReceiver receiver){
System.out.println(receiver.getInfo());
}
}
依赖关系的传递在Java开发中有三种方式
- 接口传递依赖
- 构造方法传递依赖
- setter方式传递依赖
接口传递依赖实例代码
interface IOpenAndClose{
void open(ITV tv);
}
interface ITV{
void play();
}
class OpenAndClose implements IOpenAndClose{
public void open(ITV tv){
tv.play();
}
}
构造方法传递依赖实例代码
interface IOpenAndClose{
void open();
}
interface ITV{
void play();
}
class OpenAndClose implements IOpenAndClose{
public ITV tv;
public OpenAndClose(ITV tv){
this.tv = tv;
}
public void open(){
this.tv.play();
}
}
setter方法传递依赖实例代码
interface IOpenAndClose{
void open();
void setTv(ITV tv);
}
interface ITV{
void play();
}
class OpenAndClose implements IOpenAndClose{
private ITV tv;
public void setTV(ITV tv){
this.tv = tv;
}
public void open(){
this.tv.play();
}
}
注意事项
低层模块都要有其对应的抽象类和接口,或者二者都有,这样的代码稳定性更好
里氏替换原则
产生背景
继承在给程序带来遍历的时候,也带来了弊端。继承对于程序有入侵性,增加了对象间的耦合性。如果一个类被其他类继承;当这个类需要修改时,必须要考虑到所有子类;并且当父类修改后,涉及到子类的功能可能产生故障。
- 使用继承时,遵从里氏替换原则,在子类中尽量不要重写父类的方法
- 继承使两个类的耦合性增强了,在适当的情况下,我们应该通过聚合,组合,依赖来解决问题
错误示例
class A{
public int fun1(int a,int b){
return a - b;
}
}
class B extends A{
public int fun1(int a,int b){
return a + b;
}
}
//B 类 错误 或者 不小心 地重写了父类的方法导致程序错误
修改后的代码
class Base{
//更加基础的方法
}
class A extends Base{
public int fun1(int a,int b){
return a - b;
}
}
class B extends Base{
private A a = new A();
public int fun1(int a,int b){
return a + b;
}
//仍然需要A中的fun1,但不需要进行重写
public int fun3(int a,int b){
return this.a.fun1(a,b);
}
}
//这里使用的组合的方法,降低了类之间的耦合度
//同时因为B不再继承于A,因此程序员不会再认为B中fun1像A中一样做减法了
开闭原则
最重要的一个原则,前面的多个原则都是为了实现开闭原则
- 一个软件中的类,模块,函数应该对扩展开发(对提供方),对修改关闭(对使用方)
- 用抽象构建框架,用实现扩展细节
- 当软件需要变化时,最好是通过扩展软件实体来实现,而不是通过修改原有的代码来实现
错误示例
class GraphicEditor{
public void drawShape(Shape s){
if(s.m_type == 1){
drawRectangle(s);
}else if(s.m_type == 2){
drawCircle(s);
//新增的三角形
}else if(s.m_type == 3){
drawTriangle(s);
}
}
public void drawRectangle(Shape r){
System.out.println("绘制矩形");
}
public void drawCircle(Shape r){
System.out.println("绘制圆形");
}
//新增的方法
public void drawTriangle(Shape r){
System.out.println("绘制三角形");
}
}
class Shape{
int m_type;
}
class Rectangle extends Shape{
Rectangle(){
super.m_type = 1;
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
}
//新增的三角形
class Triangle extends Shape{
Triangle(){
super.m_type = 3;
}
}
从上述代码可以容易的看出,每当要新增一个形状,不只要新增一个类,而且对于Shape的使用方GraphicEditor的内部代码也需要修改,这明显违背了开闭原则
改进后的代码
class GraphicEditor{
public void drawShape(Shape s){
s.draw();
}
}
class Shape{
int m_type;
public void draw();
}
class Rectangle extends Shape{
Rectangle(){
super.m_type = 1;
}
@Override
public void draw(){
System.out.println("绘制矩形");
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
@Override
public void draw(){
System.out.println("绘制圆形");
}
}
//新增的三角形
class Triangle extends Shape{
Triangle(){
super.m_type = 3;
}
@Override
public void draw(){
System.out.println("绘制三角形");
}
}
这样使用方法的代码不会因为新增类的变动而修改代码
现在回头看改善后的依赖倒置原则和该示例非常相近,都是使用一个抽象类(接口)作为一组关联关系的依赖
迪米特法则
- 一个类对自己依赖的类所知的越少越好,即被依赖的类不管多复杂都应该进行封装,将一个简单的接口交给其他类来使用
迪米特法则还有一个更加简单的定义:只与直接朋友通信
直接朋友定义:
如果类A中具有一个属性B;
如果类A有一个方法需要变量B;
如果类A有一个方法的返回值为B;
这样的 B 称为 A 的直接朋友
陌生的类最好不要以局部变量的类的形式出现在类的内部
例如:
class A{
publib void fun1(){
B b = new B();
}
}
错误示例
class A{
public String id;
public A(String id){
this.id = id;
}
}
class B{
public String id;
public A(String id){
this.id = id;
}
}
class A_Printer{
public List<A> getA_List(){
List<A> list = new ArrayList<>();
for(i=0;i=3;i++){
A a = new A(i):
list.add(a);
}
return list;
}
}
class B_Printer{
public List<B> getB_List(){
List<B> list = new ArrayList<>();
for(i=0;i=3;i++){
B a = new B(i):
list.add(a);
}
return list;
}
public void printA_and_B(A_Printer aprinter){
//printA
List<A> AList = aprinter.getA_List();
for(i=0;i=3;i++){
for(A a:AList){
System.out.println(a.id);
}
}
//printB
List<B> BList = this.getB_List();
for(i=0;i=3;i++){
for(B b:BList){
System.out.println(b.id);
}
}
}
}
//简写main函数
main(){
B_Printer b = new B_Printer();
b.printA_and_B(new A_Printer);
}
明显可见 List<A> AList 并不是类B的直接朋友,这使得类之间的耦合度上升了
因此需要对这一部分的代码进行修改
修改思路:只需要把不是直接朋友的类那一部分去掉,也就是说注释中 //PrintA 的那一段不应该直接出现在方法printA_and_B中,而应该由类A_Printer实现并通过某个方法直接提供给类B——Printer来使用。
修改后的代码
class A{
public String id;
public A(String id){
this.id = id;
}
}
class B{
public String id;
public A(String id){
this.id = id;
}
}
class A_Printer{
public List<A> getA_List(){
List<A> list = new ArrayList<>();
for(i=0;i=3;i++){
A a = new A(i):
list.add(a);
}
return list;
}
public void printA(){
//printA
List<A> AList = this.getA_List();
for(i=0;i=3;i++){
for(A a:AList){
System.out.println(a.id);
}
}
}
}
class B_Printer{
public List<B> getB_List(){
List<B> list = new ArrayList<>();
for(i=0;i=3;i++){
B a = new B(i):
list.add(a);
}
return list;
}
public void printA_and_B(A_Printer aprinter){
//printA
A.printA();
//printB
List<B> BList = this.getB_List();
for(i=0;i=3;i++){
for(B b:BList){
System.out.println(b.id);
}
}
}
}
//简写main函数
main(){
B_Printer b = new B_Printer();
b.printA_and_B(new A_Printer);
}
合成复用原则
中心思想:尽量使用合成或者聚合的方式,而不是继承的方式
依赖关系
class B{
public void fun1(A a){
a.fun2();
}
}
聚合关系
class B{
private A a;
public void fun1(){
a.fun2();
}
}
组合关系
class B{
private A a = new A();
public void fun1(){
a.fun2();
}
}
设计原则的中心思想
- 找出程序中可能需要变化的地方,把它们独立出来,不要和不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合而努力