一天一个设计模式---分类与六大原则

一、设计模式的分类

设计模式可以分为三大类:

(1) 创建型模式
  1. 抽象工厂模式
  2. 生成器模式
  3. 工厂方法模式
  4. 原型模式
  5. 单例模式
(2) 结构型模式
  1. 适配器模式
  2. 桥接模式
  3. 组合模式
  4. 装饰者模式
  5. 外观模式
  6. 享元模式
  7. 代理模式
(3) 行为模式
  1. 责任链模式
  2. 命令模式
  3. 解释器模式
  4. 迭代器模式
  5. 中介者模式
  6. 备忘录模式
  7. 观察者模式
  8. 状态模式
  9. 策略模式
  10. 模板方法模式
  11. 访问者模式
    设计模式之间关系

二、设计模式六大原则

1. 单一职责原则

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。如果一个类包含多种职责,就应该把类拆分。

场景:如果类A有两个职责:d1,d2。当职责d1需要修改时,可能会导致原本运行正常的职责d2功能产生问题。

方案:如果一个类包含多种职责,就应该把类拆分。分别建立两个类A、B,让A负责d1,B负责d2。当需要修改某一职责,那么将不会对另外一个功能产生影响。

2. 里氏替换原则

定义:这一原则与继承紧密相关。如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。所有引用基类的地方必须能透明地使用其子类的对象。

场景:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

// 我们需要完成一个两数相减的功能,由类A来负责。
class A{
	public int func1(int a, int b){
		return a-b;
	}
}

public class Client{
	public static void main(String[] args){
		A a = new A();
		System.out.println("100-50="+a.func1(100, 50));
		System.out.println("100-80="+a.func1(100, 80));
	}
} 
运行结果:

100-50=50

100-80=20

如果我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。

那么类B则要完成两个功能:

  • 两数相减
  • 两数相加,然后再加100

由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:

class B extends A{
	public int func1(int a, int b){
		return a+b;
	}
	
	public int func2(int a, int b){
		return func1(a,b)+100;
	}
}

public class Client{
	public static void main(String[] args){
		B b = new B();
		System.out.println("100-50="+b.func1(100, 50));
		System.out.println("100-80="+b.func1(100, 80));
		System.out.println("100+20+100="+b.func2(100, 20));
	}
} 
类B完成后,运行结果:

100-50=150

100-80=180

100+20+100=220

方案:当使用继承时,遵循里氏替换原则。子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的形参要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。

3. 依赖倒置原则

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

场景:类A的方法依赖类B,如果需要A通过C来实现同样的功能,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。


class Book{
	public String getContent(){
		return "书中的故事";
	}
}

class Mother{
	public void narrate(Book book){
		System.out.println(book.getContent());
	}
}

public class Client{
	public static void main(String[] args){
		Mother mother = new Mother();
		mother.narrate(new Book());
	}
} 
运行结果:

书中的故事

假如有一天,我们不是给书而是给一份报纸,让这位母亲读下报纸上的事。报纸代码如下:

class Newspaper{
	public String getContent(){
		return "报纸上的新闻";
	}
} 

我们会发现一个问题,我们需要修改原来的narrate,将入参修改。或者,我们可以新建一个方法。但是,这样也需要修改Mother的代码。使用面向接口编程,会很好的解决这个问题

public void narrate(Newspaper papper){
		System.out.println(papper.getContent());
}

方案:通过面向接口编程,将方法的参数类型设置为接口。而之前的具体实现类来实现接口中的方法。

class Book implements IReader{
	public String getContent(){
		return "书中的故事";
	}
}

class Newspaper implements IReader{
	public String getContent(){
		return "报纸上的新闻";
	}
} 

class Mother{
	public void narrate(IReader reader){
		System.out.println(reader.getContent());
	}
}

public class Client{
	public static void main(String[] args){
		Mother mother = new Mother();
		mother.narrate(new Book());
		mother.narrate(new Newspaper());
	}
}

4. 接口隔离原则

定义:实现类不应该依赖它不需要实现接口具体方法的接口。

场景:接口A需要实现方法a、b、c,具体实现类B实现方法a、b,实现类C实现a、c。所以B,C需要实现它们不需要的方法。

方案:将接口A继续拆分为独立的几个接口,类B和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

5. 迪米特法则(最少知道原则)

定义:一个对象应该对其他对象保持最少的了解。也就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

场景:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

// 总公司员工
public class Employee {
}

// 子公司员工
public class ChildEmployee {
}


public class ChildEmployeeService {
    // 输出所有子公司员工
    public void printAllChildEmployee() {
        List<ChildEmployee> list = new ArrayList<ChildEmployee>();
        for(int i=0; i<30; i++){
            ChildEmployee emp = new ChildEmployee();
            list.add(emp);
        }
        
        for (ChildEmployee childEmployee : list) {
            System.out.println(childEmployee);
        }
    }
    
}


public class EmployeeService {
    // 输出所有总公司员工
    public void printAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();
        for (int i = 0; i < 30; i++) {
            Employee emp = new Employee();
            list.add(emp);
        }

        for (Employee employee : list) {
            System.out.println(employee);
        }
    }

    // 输出所有公司员工
    public void printEmployee() {
        // 输出所有子公司员工
        List<ChildEmployee> list = new ArrayList<ChildEmployee>();
        for (int i = 0; i < 30; i++) {
            ChildEmployee emp = new ChildEmployee();
            list.add(emp);
        }

        for (ChildEmployee childEmployee : list) {
            System.out.println(childEmployee);
        }

        // 输出所有总公司员工
        List<Employee> list2 = new ArrayList<Employee>();
        for (int i = 0; i < 30; i++) {
            Employee emp = new Employee();
            list2.add(emp);
        }

        for (Employee employee : list2) {
            System.out.println(employee);
        }
    }
}

// 测试用例
public class Test {
    public static void main(String[] args) {
        EmployeeService e = new EmployeeService();
        e.printEmployee();
    }
}

现在这个设计的主要问题出在EmployeeService中,根据迪米特法则,只与直接的朋友发生通信,而ChildEmployee类和EmployeeService类并不应该有直接关系,从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,而和分公司员工的相关操作应该交给分公司来处理。

方案:软件编程的总的原则:低耦合,高内聚。尽量降低类与类之间的耦合。

// 修改EmployeeService类printEmployee方法,让总公司调用子公司的业务方法来实习和子公司员工的解耦合
public void printEmployee(ChildEmployeeService service) {
        // 输出所有子公司员工
        service.printAllChildEmployee();

        // 输出所有总公司员工
        List<Employee> list2 = new ArrayList<Employee>();
        for (int i = 0; i < 30; i++) {
            Employee emp = new Employee();
            list2.add(emp);
        }

        for (Employee employee : list2) {
            System.out.println(employee);
        }
 }

public class Test {
    public static void main(String[] args) {
        EmployeeService e = new EmployeeService();
        e.printEmployee(new ChildEmployeeService());
    }
}

6. 开闭原则

定义:对扩展开放,对修改关闭

场景:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误

方案:当软件需要变化时,不应该通过修改已有的代码来实现变化,而是尽量通过扩展原有代码。这是为了使程序的扩展性好,易于维护和升级。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值