设计模式总纲

设计模式是代码设计经验的总结,有助于提高系统的健壮性和可扩展性。文章介绍了设计模式的六个主要原则:单一职责原则(每个类只负责一项功能)、里氏替换原则(子类可替换父类并正常工作)、接口隔离原则(接口行为应尽可能小)、依赖倒置原则(依赖于抽象而非具体实现)、迪米特原则(减少类间耦合)和开-闭原则(对修改关闭,对扩展开放)。通过实例说明了如何应用这些原则来优化代码结构和降低耦合度。
摘要由CSDN通过智能技术生成

简介

说到设计模式,当初第一次听到时,第一反应就是很深奥,完全理解不了这个概念到底是什么意思,下面我先从网上摘录一份定义。
设计模式(Designpattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
上面是百度当中的解释,来解释一下这句简单的话的含义,几个关键词。
反复使用:这个不用过多解释,设计模式被使用太多了
多数人知晓:这个就不需要过多解释了。
分类编目:就是说可以找到一些特征去划分这些设计模式,从而进行分类。
代码设计经验:这句很重要,设计经验的总结,也就是说设计模式,是为了指导设计而从经验中总结出来的套路。

为什么学习设计模式

在工作中维护一个系统的时候,有时候一个很小的需求都需要大量的改动。代码逻辑大批量的耦合致使改动大,且每一个改动都需要梳理改模块的全部逻辑
或者在前人的代码上写东西的时候,发现前人用了大量的设计模式,代码挑来跳来跳去完全摸不着头脑😂

设计模式的好处以及注意点

设计模式可以帮助我们改善系统的设计,增强系统的健壮性、可扩展性,为以后铺平道路。
但是过多的模式也会系统变的复杂。所以当我们第一次设计一个系统时,请将你确定的变化点处理掉,不确定的变化点千万不要假设它存在
因为我们完全可以使用另外一种手法来容纳我们的变化点,那就是重构

指导原则:六大原则

指导原则是指导设计模式的规则,但是原则是死的,人是活的,所以并不是要完完全全遵守这些规则,否则为何数据库会有逆范式,只是在可能的情况下,请尽量遵守。

单一职责原则

描述 : 每个类都只负责单一的功能,并把这个功能做到极致
看下面这个🌰

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;


public class Calculator {

    public int add() throws NumberFormatException, IOException{
        File file = new File("E:/data.txt");
        BufferedReader br = new BufferedReader(new FileReader(file));
        int a = Integer.valueOf(br.readLine());
        int b = Integer.valueOf(br.readLine());
        return a+b;
    }

    //test
    public static void main(String[] args) throws NumberFormatException, IOException {
        Calculator calculator = new Calculator();
        System.out.println("result:" + calculator.add());
    }
}

上述例子的作用是从文件中读取两个数进行加和。该计算类中除了计算能力之外还耦合了读取数据的能力,如果不是计算文件中的数,而是读取配置文件中的数据,那这个计算类岂不是无法使用。
下面分离出一个Reader类

package com.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;


public class Reader {
    
    int a,b;
    
    public Reader(String path) throws NumberFormatException, IOException{
        BufferedReader br = new BufferedReader(new FileReader(new File(path)));
        a = Integer.valueOf(br.readLine());
        b = Integer.valueOf(br.readLine());
    }
    
    public int getA(){
        return a;
    }
    
    public int getB(){
        return b;
    }
}

计算类

package com.test;
import java.io.IOException;


public class Calculator {

    public int add(int a,int b){
        return a + b;
    }

    //test
    public static void main(String[] args) throws NumberFormatException, IOException {
        Reader reader = new Reader("E:/data.txt");
        Calculator calculator = new Calculator();
        System.out.println("result:" + calculator.add(reader.getA(),reader.getB()));
    }
    
}

将一个类拆分为两个类,会发现对于计算类而言,只需要计算而不需要关心计算数据的来源。在此基础上构建更多的计算功能,也不需要关系许多冗余的代码,更容易做到功能的极致

里氏替换原则

描述:一个子类可以替换掉父类且正常工作,那么翻译成比较容易理解的话,就是说,子类一般不该重写父类的方法,因为父类的方法一般都是对外公布的接口,是具有不可变性的,你不该将一些不该变化的东西给修改掉。
不过就算如此,如果你真的遇见了不得不重写父类方法的场景,那么请你考虑,你是否真的要把这个类作为子类出现在这里,或者说这样做所换来的是否能弥补你失去的东西,比如子类无法代替父类工作,那么就意味着如果你的父类可以在某一个场景里工作的很正常,那么你的子类当然也应该可以,否则就会出现下述场景。
比如我们有某一个类,其中有一个方法,调用了某一个父类的方法。
//某一个类

public class SomeoneClass {
    //有某一个方法,使用了一个父类类型
    public void someoneMethod(Parent parent){
        parent.method();
    }
}

父类代码

public class Parent {

    public void method(){
        System.out.println("parent method");
    }
}

结果我有一个子类把父类的方法给覆盖了,并且抛出了一个异常

public class SubClass extends Parent{

    //结果某一个子类重写了父类的方法,说不支持该操作了
    public void method() {
        throw new UnsupportedOperationException();
    }
    
}

这个异常是运行时才会产生的,也就是说,我的SomeoneClass并不知道会出现这种情况,结果就是我调用下面这段代码的时候,本来我们的思维是Parent都可以传给someoneMethod完成我的功能,我的SubClass继承了Parent,当然也可以了,但是最终这个调用会抛出异常

public class Client {

    public static void main(String[] args) {
        SomeoneClass someoneClass = new SomeoneClass();
        someoneClass.someoneMethod(new Parent());
        //替换
        someoneClass.someoneMethod(new SubClass());
    }
}

这就相当于埋下了一个个陷阱,因为本来我们的原则是,父类可以完成的地方,我用子类替代是绝对没有问题的,但是这下反了,我每次使用一个子类替换一个父类的时候,我还要担心这个子类有没有给我埋下一个上面这种炸弹

接口隔离原则

描述:也称接口最小化原则,强调的是一个接口的行为应该尽可能的小。这和单一职责原则的底层思想相同,即少让其他类知道自己的细节且自己的细节尽可能的少透出。但是单一职责原则描述的对象是底层细节,而接口隔离原则描述的是高层抽象
如果你做不到这一点你经常会发现这样的状况,一个类实现了一个接口,里面很多方法都是空着的,只有个别几个方法实现了。
这样做不仅会强制实现的人不得不实现本来不该实现的方法,最严重的是会给使用者造成假象,即这个实现类拥有接口中所有的行为,结果调用方法时却没收获到想要的结果。
比如我们设计一个手机的接口时,就要手机哪些行为是必须的,要让这个接口尽量的小,或者通俗点讲,就是里面的行为应该都是这样一种行为,就是说只要是手机,你就必须可以做到的。
上面就是接口隔离原则这个挑剔女所挑剔的地方,假设你没有满足她,你或许会写出下面这样的手机接口

public interface Mobile {

    public void call();//手机可以打电话
    
    public void sendMessage();//手机可以发短信
    
    public void playBird();//手机可以玩愤怒的小鸟?
    
}

上面第三个行为明显就不是一个手机应该有的,或者说不是一个手机必须有的,那么上面这个手机的接口就不是最小接口,假设我现在的非智能手机去实现这个接口,那么playBird方法就只能空着了,因为它不能玩。
所以我们更好的做法是去掉这个方法,让Mobile接口最小化,然后再建立下面这个接口去扩展现有的Mobile接口。

public interface SmartPhone extends Mobile{

    public void playBird();//智能手机的接口就可以加入这个方法了
    
}

这样两个接口就都是最小化的了,这样我们的非智能手机就去实现Mobile接口,实现打电话和发短信的功能,而智能手机就实现SmartPhone接口,实现打电话、发短信以及玩愤怒的小鸟的功能,两者都不会有多余的要实现的方法。
底层逻辑是对事物抽象层级的划分

依赖倒置原则

描述:高层抽象不应该依赖底层细节,底层细节应该依赖高层抽象。即描述的是一个现实当中的事实,即实现都是易变的,而只有抽象是稳定的,所以当依赖于抽象时,实现的变化并不会影响客户端的调用。
比如上述的计算器例子,我们的计算器其实是依赖于数据读取类的,这样做并不是很好,因为如果我的数据不是文件里的了,而是在数据库里,这样的话,为了不影响你现有的代码,你就只能将你的Reader类整个改头换面。
或者还有一种方式就是,你再添加一个DBReader类,然后把你所有使用Reader读取的地方,全部手动替换成DBReader,这样其实也还可以接受,那假设我有的从文件读取,有的从数据库读取,有的从XML文件读取,有的从网络中读取,有的从标准的键盘输入读取等等。
你想怎么办呢?
所以我们最好的做法就是抽象出一个抽象类或者是接口,来表述数据读取的行为,然后让上面所有的读取方式所实现的类都实现这个接口,而我们的客户端,只使用我们定义好的接口,当我们的实现变化时,我只需要设置不同的实际类型就可以了,这样对于系统的扩展性是一个大大的提升。
针对上面简单的数据读取,我们可以定义如下接口去描述

public interface Reader {

    public int getA();
    
    public int getB();
}

让我们原来的Reader改名为FileReader去实现这个接口,这样计算器就依赖于抽象的接口,这个依赖是非常稳定的,因为不论你以后要从哪读取数据,你的两个获取数据的方法永远都不会变。
这样,我们让DBReader,XMLReader,NETReader,StandardOutPutStreamReader等等,都可以实现Reader这个接口,而我们的客户端调用依赖于一个Reader,这样不管数据是从哪来的,我们都可以应对自如,因为我根本不关心你是什么Reader,我只知道你能让我获得A和B这两个值就行了

迪米特原则

描述 : 即一个类尽量不要知道太多其他类的东西,也不要让自己的太多东西被其他类知道
所谓高内聚就是尽可能将一个类的细节全部写在这个类的内部,不要漏出来给其他类知道,否则其他类就很容易会依赖于这些细节,这样类之间的耦合度就会急速上升,这样做的后果往往是一个类随便改点东西,依赖于它的类全部都要改。
举个🌰

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;


public class Reader {
    
    int a,b;
    
    private String path;
    
    private BufferedReader br;
    
    public Reader(String path){
        this.path = path;
    }
    
    public void setBufferedReader() throws FileNotFoundException{
        br = new BufferedReader(new FileReader(new File(path)));
    }
    
    public void readLine() throws NumberFormatException, IOException{
        a = Integer.valueOf(br.readLine());
        b = Integer.valueOf(br.readLine());
    }
    
    public int getA(){
        return a;
    }
    
    public int getB(){
        return b;
    }
}

Reader类改成上述这个样子,显然它给其他的类透漏了太多细节,让别人知道了它的太多细节,这样我客户端调用的时候就很可能写成如下形式。

public class Client {

    public static void main(String[] args) throws Exception {
        Reader reader = new Reader("E:/test.txt");
        reader.setBufferedReader();
        reader.readLine();
        int a = reader.getA();
        int b = reader.getB();
        //以下用于计算等等
    }
}

这样客户端就依赖于reader的多个行为才能最终获取到A和B两个数值,这时候两个类的耦合度就太高了,我们更好的做法使用访问权限限制将二者都给隐藏起来不让外部调用的类知道这些。就像下面这样。

public class Reader {

    int a,b;
    private String path;
    private BufferedReader br;
    public Reader(String path) throws Exception{
        super();
        this.path = path;
        setBufferedReader();
        readLine();
    }
    //注意,我们变为私有的方法
    private void setBufferedReader() throws FileNotFoundException{
        br = new BufferedReader(new FileReader(path));
    }
    //注意,我们变为私有的方法
    private void readLine() throws NumberFormatException, IOException{
        a = Integer.valueOf(br.readLine());
        b = Integer.valueOf(br.readLine());
    }
    
    public int getA(){
        return a;
    }
    
    public int getB(){
        return b;
    }
}

我们最终将两个方法都变为私有封装在Reader类当中,这样外部的类就无法知道这两个方法了,所以迪米特原则虽说是指的一个类应当尽量不要知道其他类太多细节,但其实更重要的是一个类应当不要让外部的类知道自己太多。两者是相辅相成的,只要你将类的封装性做的很好,那么外部的类就无法依赖当中的细节。

开-闭原则

描述:对修改关闭,对扩展开放
就是说我任何的改变都不需要修改原有的代码,而只需要加入一些新的实现,就可以达到我的目的,这是系统设计的理想境界
这个原则更像是前五个原则的总纲,前五个原则就是围着它转的,只要我们尽量的遵守前五个原则,那么设计出来的系统应该就比较符合开闭原则了,相反,如果你违背了太多,那么你的系统或许也不太遵循开闭原则

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值