设计模式6大原则

一、单一职责原则

      1、官方定义:

            单一职责原则,英文缩写SRP,全称 Single Responsibility Principle。

           《在架构整洁之道》一书中关于单一职责原则的描述是这样的:一个类

            或者模块只负责一个职责(或者功能)

      2、通俗解释:

           一个类只负责一个职责或者功能;也就是说在类的设计中,我们不要设计

          大而全的类,而是要设计粒度小功能单一的类。

     3、作用

           1)实现类的高内聚

           2)实现代码的高内聚、低耦合

     4、如何判断一个类的职责是否单一?

          可以基于下边几个方向来判断类的职责是否单一,下边是不满足单一职责的4种情况:

          1)类中的代码行数、属性个数或函数过多,这时需要思考一下该类的职责是否单一

          2)类依赖的其他类过多

          3)类中私有方法过多

          4)类中大量的方法总是操作类中的几个属性     

二、开闭原则

       1、官方定义

             一般认为最早提出开闭原则(Open-Close Principle,简称OCP)的是伯特兰-迈耶。

             他在1988年发表的《面向对象软件构造》中给出的。

             面向对象编程领域中。开闭原则规定软件中的对象、类、模块和函数 对扩展应该是

             开放的,但对于修改是封闭的。这意味着应该用抽象定义结构,用具体实现扩展细节

             ,以此确保软件系统开发和维护过程的可靠性。

       2、通俗解释

            开闭原则就是 对扩展开放,对修改关闭;

            开闭原则并不是完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发

        3、开闭原则优点(作用):

                    1)新老逻辑解耦,需求发生改变不会影响老业务逻辑

                    2)改动成本小,只需要增加新逻辑,不需要改动老逻辑(不一定,但最好小

                          范围修改)

                    3)提供代码的稳定性和可扩展性

        4、开闭原则场景示例

            

             示例代码如下:

             

/**
 * 定义通讯协议接口
 */
public interface ReportProtocol {
    /**
     * 解释保温
     */
    void msgExplain();

    /**
     * 编制报文
     */
    void msgCompile();

    /**
     * 校验报文
     */
    void msgCheck();
}


/*******************************************************
 *  5266通讯协议
 *  
 *******************************************************/
public class ReportProtocol5266 implements ReportProtocol{

    @Override
    public void msgExplain() {
        System.out.println("++++++++++++++++解释报文 5266 ++++++++++++++++++++");
    }

    @Override
    public void msgCompile() {
        System.out.println("++++++++++++++++编制报文 5266 ++++++++++++++++++++");
    }

    @Override
    public void msgCheck() {
        System.out.println("++++++++++++++++校验报文 5266 ++++++++++++++++++++");
    }
}


/*******************************************************
 * 现在要对旧的5266协议进行升级,但又不能影响老的业务?
 * 只需要新建一个类,实现协议接口 ReportProtocol,然后在调用方的类型是接口类型就可以了
 *
 *******************************************************/
public class ReportProtocol5269 implements ReportProtocol{
    @Override
    public void msgExplain() {
        System.out.println("++++++++++++++++解释报文 5269 ++++++++++++++++++++");
    }

    @Override
    public void msgCompile() {
        System.out.println("++++++++++++++++编制报文 5269 ++++++++++++++++++++");
    }

    @Override
    public void msgCheck() {
        System.out.println("++++++++++++++++校验报文 5266 ++++++++++++++++++++");
    }
}


//调用方 CallContext
public class CallContext {

    private ReportProtocol reportProtocol;

    /**
     * 解释保温
     */
    public void msgExplain(){
        reportProtocol.msgExplain();
    }

    /**
     * 编制报文
     */
    public void msgCompile(){
        reportProtocol.msgCompile();
    }

    /**
     * 校验报文
     */
    public void msgCheck(){
        reportProtocol.msgCheck();
    }


    public void setReportProtocol(ReportProtocol reportProtocol) {
        this.reportProtocol = reportProtocol;
    }
}

        4、总结:

              开闭原则是所有设计模式最核心的目标,通过开闭原则我们应该学习程序设计中的

              顶层思维,大致方向如下:

              1)抽象意识

              2)封装意识

              3)扩展意思

三、里氏替换原则

       1、官方定义

             里氏替换原则(Liskov Substitution Principle,简称 LSP)是由麻省理工学院计算机科学

             系教授芭芭拉-利斯科夫于1987年在“面向对象技术的高峰会议”(OOPSLA)上发表的一篇

              论文《数据抽象和层次》里提出的。

              她在论文中提到:如果S是T的子类型,对于S类型的任意对象,如果将他们看作是T类型

              对象,则对象的行为也理应与期望一致。

       2、通俗解释

             里氏替换原则就是:子类对象能够替换父类对象出现的任何地方,并且

                                              保证原来程序的逻辑行为不变及正确性不被破坏。

             要理解里氏替换原则,其实就要理解2个问题:

                     1)什么是替换

                           替换的前提是面向对象语言所支持的多太特性,同一个行为具有多个不同表现

                           形式或形态的能力。

                           简单的说:就是当一个方法的参数是接口类型时,这个方法可以接收所有实现过

                                              这个接口的实现类。

                     2)什么是与期望行为一致的替换?

                           在不了解派生类的情况下,仅通过接口或基类的方法,即可清楚的知道方法的行

                           为,而不管哪种派生类的实现,都与接口或基类方法的期望行为一致。

         4、里氏替换原则作用

                1)为良好的继承定义了一个规范;

                2)提高代码的健壮性,降低程序出错的可能性。

         5、里氏替换与多太的区别?

                1)多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法,是一种代码

                      实现的思路。

                2)里氏替换是一种设计原则,用来指导继承关系中子类该如何设计;在替换父类的时                          侯,不改变原有程序的逻辑及不破坏原有程序的正确性。

               

         6、场景示例

                在商城项目中,退出了三种促销方式:

                       1)满减活动、2百以上打八折

                        2)打折活动,全场9折

                        3)返现活动,消费超1000,返200

                  请设计一个计算接口以及实现类,该接口的结算方法能够对用户的最终消费金额

                  进行一个计算。计算结果需要满足上边多种促销方式。

                 

             示例代码如下:

            

/**
 * 商品促销接口
 */
public interface Istraegy {

    double realPrice(double consumer);
}


//促销分类,每个实现类表示一种促销

/*******************************************************
 * 满减活动、2百以上打八折
 *
 *******************************************************/
public class PromotionnalStraegy implements Istraegy{

    @Override
    public double realPrice(double consumer) {

        return Double.compare(consumer,200)>=0? consumer*0.8:consumer;
    }
}


/*******************************************************
 * 打折活动,全场9折
 *******************************************************/
public class RebateStraegy implements Istraegy{
    @Override
    public double realPrice(double consumer) {
        return consumer*0.9;
    }
}


/*******************************************************
 * 返现活动,消费超1000,返200
 *
 *******************************************************/
public class ReduceStraegy implements Istraegy{
    @Override
    public double realPrice(double consumer) {
        return Double.compare(consumer,1000)>= 0? consumer-200:consumer;
    }
}



/*******************************************************
 *
 *  消费者调用方
 *******************************************************/
public class CallContext {

    private Istraegy straegy;

    /**
     * 计算并返回最终费用
     * @param price
     * @return
     */
    public double culPrice(double price){
        //使用具体的商品促销策略获得实际消费金额
        double culPrice = this.straegy.realPrice(price);
        //格式化金额,保留小数点后2位
        BigDecimal decimal = new BigDecimal(culPrice);
        decimal.setScale(2,BigDecimal.ROUND_CEILING);

        return decimal.doubleValue();
    }


    public void setStraegy(Istraegy straegy) {
        this.straegy = straegy;
    }
}

四、接口隔离原则(ISP

       1、官方定义

            《代码整洁之道》作者罗伯特 C-马丁 为 “接口隔离原则” 的定义是:客户端不应该被迫

              依赖于它不使用的方法(Clients should not be forced to depend on methods they do

              not use)

              接口隔离原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上。

        2、通俗解释

              上边2个定义的含义用一句话概括就是:要为各个类建立他们需要的专用接口,而不要

               试图去建立一个很庞大的接口供所有依赖他的类去调用。

        

         3、场景示例

               微服务用户系统提供了一组跟用户相关的API给其他系统使用,比如:注册、登录、获取

               用户信息等。

              现在有个需求:后台管理系统要实现删除用户的功能,希望用户系统提供一个删除用户的

              接口;应该如何设计这个接口?

               UML类图,描述:类与类、类与接口之间的关系

              3.1)方式1:直接在原来的用户接口 UserService 中增加一个删除用户方法del,如

                        

             代码如下所示:

                   

/**
 * 接口隔离原则
 */
public interface UserService {

    /**
     * 用户注册
     * @param cellphone
     * @param password
     * @return
     */
    boolean register(String cellphone,String password);

    /**
     * 用户登录
     * @param cellphone
     * @param password
     * @return
     */
    boolean login(String cellphone,String password);

    /**
     * 根据用户id查询
     * @param userId
     * @return
     */
    UserInfo getUserInfoByUserId(Long userId);

    /**
     * 根据 cellphone 查询
     * @param userId
     * @return
     */
    UserInfo getUserInfoByCellPhone(String cellphone);

    /*
    删除用户
     */
    boolean delUserByUserId(Long userId);
    boolean delUserByCellPhone(String cellphone);
}



/*******************************************************
 * 接口隔离原则测试
 *
 *******************************************************/
public class UserServiceImpl implements UserService{
    @Override
    public boolean register(String cellphone, String password) {
        return false;
    }

    @Override
    public boolean login(String cellphone, String password) {
        return false;
    }

    @Override
    public UserInfo getUserInfoByUserId(Long userId) {
        return null;
    }

    @Override
    public UserInfo getUserInfoByCellPhone(String cellphone) {
        return null;
    }

    @Override
    public boolean delUserByUserId(Long userId) {
        return false;
    }

    @Override
    public boolean delUserByCellPhone(String cellphone) {
        return false;
    }
}

               3.2)方式2:遵循接口隔离原则,为依赖接口的类定制服务,只是提供调用者需要的

                                    方法,屏蔽不需要的方法

   

               代码如下所示:

                  

/**
 * 接口隔离原则
 */
public interface UserService {

    /**
     * 用户注册
     * @param cellphone
     * @param password
     * @return
     */
    boolean register(String cellphone,String password);

    /**
     * 用户登录
     * @param cellphone
     * @param password
     * @return
     */
    boolean login(String cellphone,String password);

    /**
     * 根据用户id查询
     * @param userId
     * @return
     */
    UserInfo getUserInfoByUserId(Long userId);

    /**
     * 根据 cellphone 查询
     * @param userId
     * @return
     */
    UserInfo getUserInfoByCellPhone(String cellphone);

   
}

/**
 * 接口隔离原则测试
 * 为依赖接口的类定制服务,只是提供调用者需要的方法,屏蔽不需要的方法
 */
public interface RestrictedUserService {

    /*
   删除用户
    */
    boolean delUserByUserId(Long userId);
    boolean delUserByCellPhone(String cellphone);
}


public class UserServiceImpl implements UserService,RestrictedUserService {
    
}

             3.3)在代码设计之初,我们可以根据数据是否更新数据,把用户接口拆分成更新接口和

                     查询接口,更新接口包含用户注册(新增)、修改和删除方法;查询接口包含 用户

                      登录、用户信息查询等查询相关的功能,示例代码如下:

                     

/**
 * 接口隔离原则测试
 * 对 接口 UserService 进行拆分,拆分成2个接口:用户修改的接口 UserUpdateService 和 用户查询的接口 UserInfoService;
 *
 * UserUpdateService 包含 用户注册、用户删除方法
 * 后边若有用户修改方法也可以放在这个接口中
 */
public interface UserUpdateService {

    /**
     * 用户注册
     * @param cellphone
     * @param password
     * @return
     */
    boolean register(String cellphone,String password);
    //修改
    boolean update(UserInfo userInfo);
    /*
   删除用户
    */
    boolean delUserByUserId(Long userId);
    boolean delUserByCellPhone(String cellphone);
}


/**
 * 接口隔离原则测试
 * 对 接口 UserService 进行拆分,拆分成2个接口:用户修改的接口 UserUpdateService 和 用户查询的接口 UserInfoService;
 *
 * UserInfoService 包含 用户登录和用户查询
 *
 */
public interface UserInfoService {

    /**
     * 用户登录
     */
    boolean login(String cellphone,String password);

    /**
     * 根据用户id查询
     */
    UserInfo getUserInfoByUserId(Long userId);

    /**
     * 根据 cellphone 查询
     */
    UserInfo getUserInfoByCellPhone(String cellphone);
}


/*******************************************************
 * 接口隔离原则测试
 * 对 接口 UserService 进行拆分,拆分成2个接口:用户修改的接口 UserUpdateService 和 用户查询的 
 * 接口 UserInfoService;
 *  
 *
 *******************************************************/
public class UserServiceImpl implements UserUpdateService,UserInfoService{
}

        4、接口隔离原则的优势(作用):

              1)将胖接口分解成多个粒度小的接口,可以提高系统的灵活性和可维护性

               2)使用多个专门的接口,还能体现出对象的层次

               3)能够减少项目中冗余的代码

        5、接口隔离原则与单一职责之间的区别?

              1)单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。

               2)单一职责原则主要是约束类,他针对的是程序中的实现细节;

                     接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

               

五、依赖倒置原则

       1、官方定义

             依赖倒置原则是罗伯特 C-马丁于1996年在C++Report 上发表的文章中提出的。

             依赖倒置原则(Deependence Inversion Principle,简称DIP)是指在设计代码架构时,

             高层模块不应该依赖于低层模块,二则都应该依赖于抽象。抽象不应该依赖于细节,

              细节应该依赖于抽象。

       2、通俗描述

              依赖倒置原则想要表达的是:由于在软件设计中,细节具有多变性,而抽象层则相对

              稳定,因此以抽象为基础搭建起来的架构 要 比以细节为基础搭建起来的架构 要稳定

              的多。

              在java语言中的表现就是:

                     1)模块之间的依赖通过抽象发生,实现类之间不直接发生依赖关系,实现类之间

                           的依赖关系是通过接口或抽象类产生的。

                      2)接口或抽象类不依赖于实现类。

                      3)实现类依赖接口或抽象类。

                所以依赖倒置原则又称之为 “面向接口编程”。

       3、场景示例

             假设我们要组装一台电脑,需要的配件有cpu、硬盘、内存条。cpu 有很多选择,如:

              Intel、AMD等,硬盘可以选择希捷、西部数据等,内存条可以选择金士顿、长江和

              三星等等。该如何设计?

             UML类图如下:

            

               示例代码如下:

              

/**
 * 依赖倒置原则测试
 * 电脑的组成之一CPU
 */
public interface Cpu {

    void run();
}


/**
 * 依赖倒置原则测试
 * 电脑的组成之一硬盘
 */
public interface HardDisk {

    void set(String data);
    String get();
}



/**
 * 依赖倒置原则测试
 * 电脑的组成之一内存条
 */
public interface Memory {

    void run();
}




public class IntelCpu implements Cpu{
    @Override
    public void run() {
        System.out.println("++++++++++++++++++IntelCpu run +++++++++++++++++++++++");
    }
}


public class XiJieHardDisk implements HardDisk{
    @Override
    public void set(String data) {
        System.out.println("=========================== set =============================");
    }

    @Override
    public String get() {
        System.out.println("=========================== get =============================");
        return null;
    }
}


public class KingstonMemory implements Memory{
    @Override
    public void run() {
        System.out.println("++++++++++++++++++ KingstonMemory run ++++++++++++++++++++");
    }
}


/*******************************************************
 * 电脑与cpu、硬盘disk、内存条memory 之间是组合关系
 *******************************************************/
public class Computer {

    private Cpu cpu;

    private HardDisk disk;

    private Memory memory;

    public Computer(){}

    public Computer(Cpu cpu, HardDisk disk, Memory memory) {
        this.cpu = cpu;
        this.disk = disk;
        this.memory = memory;
    }

    public void setCpu(Cpu cpu) {
        this.cpu = cpu;
    }

    public void setDisk(HardDisk disk) {
        this.disk = disk;
    }

    public void setMemory(Memory memory) {
        this.memory = memory;
    }

    public void run(String data){

        cpu.run();
        disk.set(data);
        memory.run();
        disk.get();
    }

}

          4、依赖倒置原则、控制反转和依赖注入之间的区别

                4.1)依赖倒置是一种然健的设计原则,主要用来指导框架层面的设计。

                4.2)控制反转是一种框架设计常用的模式,但不是具体的方法。

                         控制反转的控制是对程序执行流程的一种的控制;反转指的是在

                         使用框架之前程序员自己控制程序的执行流程,使用框架后程序执行

                         交给框架控制。

                         控制反转指的是流程控制的反转。

                 4.3)依赖注入,是控制反转的一种具体实现,是一种具体的编码技巧。

六、迪米特法则(LKP)

       1、官方定义

             迪米特法则是1987年由美国Northeastern University的 Ian Holland(伊恩 霍兰德)提出,

             被UML创始人之一Booch(布奇)等人普及。

              迪米特法则又叫最少知识原则(Least Knowledge Principle,简称:LKP),指的是一个

              类或者模块对其他 类或模块 了解的越少越好。

        2、通俗描述

              简单的说 迪米特法则想要表达的思想就是:不该有直接依赖关系之间的类之间,不要

               有依赖;有依赖关系的类之间,尽量只依赖必要的接口。

               迪米特法则又可理解为 类或模块与其他 类或模块 之间互不了解,通过中间人的方式

               将他们组合起来,从而实现弱耦合

               注意:在项目中不要过多的使用迪米特法则,否则可能会产生大量的“中间类”,从而

                          加重代码的维护成本

        3、场景示例

              娱乐明细wang,由于全身心投入艺术,所有日常事务由经纪人负责处理,如与粉丝

              见面会、和媒体公司业务洽谈等等。这里的经济人是明星的朋友,粉丝和媒体公司

              是陌生人,所以适用迪米特法则。

              用UML图描述上边明星、经纪人,粉丝/媒体公司之间的关系

              

              经纪人与 明星、粉丝和媒体公司是聚合关系,经纪人将明星、粉丝和媒体公司三者

              聚合在一起。示例代码如下:

          

/*******************************************************
 *
 * 明星类
 * 
 *******************************************************/
@Data
public class Star {

    private String name;

}


/*******************************************************
 * 粉丝类
 * 
 *******************************************************/
@Data
public class Fans {

    private String name;
    private String address;


}

/*******************************************************
 *
 * 媒体公司类
 *
 *******************************************************/
public class Compony {

    private String name;

}




/*******************************************************
 * 迪米特法则测试
 * 经纪人类,
 * 经纪人把明星、粉丝和媒体公司整合在一起,Agent相当于一个中介
 *
 *******************************************************/
@Data
public class Agent {

    private Star star;

    private Fans fans;

    private Compony compony;

    //粉丝见面会
    public void metting(){
        System.out.println(star.getName() +"与"+fans.toString() +"见面");
    }

    //洽谈合作
    public void business(){
        System.out.println(star.getName()+"与"+compony.toString()+"洽谈业务");
    }
}

                  

  • 57
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值