设计模式七大原则;
- 单一职责原则
- 接口隔离原则
- 依赖倒转(倒置)原则
- 里氏替换原则
- 开闭原则
- 狄米特法则
- 合成复用法则
介绍:
下述案例来自大佬:https://zhuanlan.zhihu.com/p/24614363
(1).单一职责
介绍:对类来说,一个类应该负责一项职责,如果A负责两个不同的职责1、职责2,当职责1的需求变更改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2
class Animal{
public void breathe(String animal){
System.out.println(animal + "呼吸空气");
}
public void breathe2(String animal){
System.out.println(animal + "呼吸水");
}
}
public class Client{
public static void main(String[] args){
Animal animal = new Animal();
animal.breathe("牛");
animal.breathe("羊");
animal.breathe("猪");
animal.breathe2("鱼");
}
}
(2).接口隔离原则 ISP
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
如果是这种实现方式
interface I {
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
class A{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method2();
}
public void depend3(I i){
i.method3();
}
}
class B implements I{
// 类 B 只需要实现方法 1,2, 3,而其它方法它并不需要,但是也需要实现
public void method1() {
System.out.println("类 B 实现接口 I 的方法 1");
}
public void method2() {
System.out.println("类 B 实现接口 I 的方法 2");
}
public void method3() {
System.out.println("类 B 实现接口 I 的方法 3");
}
public void method4() {}
public void method5() {}
}
class C{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method4();
}
public void depend3(I i){
i.method5();
}
}
class D implements I{
// 类 D 只需要实现方法 1,4,5,而其它方法它并不需要,但是也需要实现
public void method1() {
System.out.println("类 D 实现接口 I 的方法 1");
}
public void method2() {}
public void method3() {}
public void method4() {
System.out.println("类 D 实现接口 I 的方法 4");
}
public void method5() {
System.out.println("类 D 实现接口 I 的方法 5");
}
}
public class Client{
public static void main(String[] args){
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend2(new D());
c.depend3(new D());
}
}
缺点:可以看出,如果接口定义的过于臃肿,只要接口中出现的方法,不管依赖于它的类是否需要该方法,实现类都必须去实现这些方法,这就不符合接口隔离原则
interface I1 {
public void method1();
}
interface I2 {
public void method2();
public void method3();
}
interface I3 {
public void method4();
public void method5();
}
class A{
public void depend1(I1 i){
i.method1();
}
public void depend2(I2 i){
i.method2();
}
public void depend3(I2 i){
i.method3();
}
}
class B implements I1, I2{
public void method1() {
System.out.println("类 B 实现接口 I1 的方法 1");
}
public void method2() {
System.out.println("类 B 实现接口 I2 的方法 2");
}
public void method3() {
System.out.println("类 B 实现接口 I2 的方法 3");
}
}
class C{
public void depend1(I1 i){
i.method1();
}
public void depend2(I3 i){
i.method4();
}
public void depend3(I3 i){
i.method5();
}
}
class D implements I1, I3{
public void method1() {
System.out.println("类 D 实现接口 I1 的方法 1");
}
public void method4() {
System.out.println("类 D 实现接口 I3 的方法 4");
}
public void method5() {
System.out.println("类 D 实现接口 I3 的方法 5");
}
}
- 接口隔离原则的思想在于建立单一接口,尽可能地去细化接口,接口中的方法尽可能少
- 但是凡事都要有个度,如果接口设计过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
(3)依赖倒转原则
高层模块不应该依赖低层模块,二者都应该于抽象。进一步说,抽象不应该依赖于细节,细节应该依赖于抽象。依赖倒转原则的核心是面向接口编程
思考下面这样一个场景:母亲给孩子讲故事,只要给她一本书,她就可照着书给孩子讲故事了。代码如下:
class Book{
public String getContent(){
return "这是一个有趣的故事";
}
}
class Mother{
public void say(Book book){
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.say(new Book());
}
}
假如有一天,给的是一份报纸,而不是一本书,让这个母亲讲下报纸上的故事,报纸的代码如下:
class Newspaper{
public String getContent(){
return "这个一则重要的新闻";
}
}
然而这个母亲却办不到,应该她只会读书,这太不可思议,只是将书换成报纸,居然需要修改 Mother 类才能读,假如以后需要换成了杂志呢?原因是 Mother 和 Book 之间的耦合度太高了,必须降低他们的耦合度才行。
我们可以引入一个抽象接口 IReader 读物,让书和报纸去实现这个接口,那么无论提供什么样的读物,该母亲都能读。代码如下:
interface IReader{
public String getContent();
}
class Newspaper implements IReader {
public String getContent(){
return "这个一则重要的新闻";
}
}
class Book implements IReader{
public String getContent(){
return "这是一个有趣的故事";
}
}
class Mother{
public void say(IReader reader){
System.out.println("妈妈开始讲故事");
System.out.println(reader.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.say(new Book());
mother.say(new Newspaper());
}
}
这样修改之后,以后无论提供什么样的读物,只要去实现了 IReader 接口之后就可以被母亲读。实际情况中,代表高层模块的 Mother 类将负责完成主要的业务逻辑,一旦需要对它进行修改,引入错误的风险极大。所以遵循依赖倒转原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。
(4)里氏替换原则
里氏替换原则的重点在不影响原功能,而不是不覆盖原方法
父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义
(5)开闭原则
- 1.开闭原则是编程中最重要、最基础的设计原则
- 2.一个软件实体如类,模块和函数应该对扩展开放,对修改关闭,用抽象构建框架,用实现设计细节;
- 3.软件需要变化的时候,尽量通过扩展软件实体的行为来实现变化,而不是修改已有的代码,
- 4.编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则
开放-关闭原则表示软件实体 (类、模块、函数等等) 应该是可以被扩展的,但是不可被修改;优点
- 能够扩展已存在的系统,能够提供新的功能满足新的需求,因此该软件有着很强的适应性和灵活性。
- 已存在的模块,特别是那些重要的抽象模块,不需要被修改,那么该软件就有很强的稳定性和持久性。
举个简单例子,这里有个生产电脑的公司,根据输入的类型,生产出不同的电脑,代码如下:
interface Computer {}
class Macbook implements Computer {}
class Surface implements Computer {}
class Factory {
public Computer produceComputer(String type) {
Computer c = null;
if(type.equals("macbook")){
c = new Macbook();
}else if(type.equals("surface")){
c = new Surface();
}
return c;
}
}
显然上面的代码违背了开放 - 关闭原则,如果需要添加新的电脑产品,那么修改 produceComputer 原本已有的方法,正确的方式如下:
interface Computer {}
class Macbook implements Computer {}
class Surface implements Computer {}
interface Factory {
public Computer produceComputer();
}
class AppleFactory implements Factory {
public Computer produceComputer() {
return new Macbook();
}
}
class MSFactory implements Factory {
public Computer produceComputer() {
return new Surface();
}
}
正确的方式应该是将 Factory 抽象成接口,让具体的工厂(如苹果工厂,微软工厂)去实现它,生产它们公司相应的产品,这样写有利于扩展,如果这是需要新增加戴尔工厂生产戴尔电脑,我们仅仅需要创建新的电脑类和新的工厂类,而不需要去修改已经写好的代码。
(6)狄米特法则
迪米特法则又称为 最少知道原则,它表示一个对象应该对其它对象保持最少的了解。通俗来说就是,只与直接的朋友通信。