zoukankan      html  css  js  c++  java
  • 设计模式--7大原则

    1_1.设计模式

    讲师:Tom

    时长:1h8min

    Date:4/10/2020 8:52-9:30

    前提知识:

           设计模式,spring相关

    适合人群:

                 

    1.为什么要从设计模式学起

    为了解耦,总结前人的经验、方便阅读代码。

    7大设计原则:

           》开闭原则---对扩展开放,对修改关闭

           》单一职责原则---一个方法只做一件事

           》依赖倒置原则---

           》接口隔离原则---尽量保证接口纯洁性

           》迪米特法则---最少依赖原则----封装与隐藏

           》里氏替换原则

           》合成复用原则----多用聚合,少用继承  

    学习它,可以更好的优化,重构代码:

    如:多分支if结构-----》提取类的方式,

    好处:

           》写出更加优雅的代码

           》帮助重构代码

           》经典框架中大量使用设计模式   

    Spring用到的设计模式:

    》工厂模式

    》装饰者模式

    》代理模式—aop

    >单例模式

    》委派模式 DispatcherServlet

    >策略模式 HandlerMapping

    >适配器模式 handlerAdapter

    >模板方法模式 jdbcTemplate

    >观察者模式ContextLoaderListener

    推荐学习资料:

    》《软件架构设计七大原则》课件

    》《大话设计模式》

    》《Head First设计模式》

    》《设计模式-可复用面向对象软件的基础》

    》《Effective Java》

    2.工厂模式

    基于spring

    Spring Ioc 工厂、单例

    Spring aop 代理、观察者

    Spring mvc 适配器

    Spring jdbc 模板方法

    2.1.什么是工厂模式?

    2.1.1简单工厂模式Simple Factory Pattern

    由工厂对象决定创建出哪一种产品类的实例。【工厂创建对象,对象创建过程封装,隐藏起来】

    属于创建型模式,不属于GOF,23种设计模式。

    2.1.1.1.代码实现

     1.1.1.jdbc连接数据库

    步骤如下:

    1.注册驱动,获致连接Connection

    2.创建Statement对象

    3.execute()执行sql

    4.把结果集转换成pojo对象

    5.关闭资源

    思考:

    当项目较大,直接使用原生api会带来什么问题?

    》代码大量重复

    》结果集处理太复杂,麻烦

    》连接管理不方便

    》sql语句硬编码

           我需要关闭资源,如果忘记关闭,可能造成数据库服务异常。

           我们对数据库的业务逻辑,与资源管理是耦合在一起的,对开发非常不利。

           为了解决这些问题,出现一框架,如:

    Mybatis,hibernate,spring-jdbc,

    Apache DbUtils

           >QueryRunner

           >ResultSetHandler

    它主要解决了结果集封装问题。它支持不同数据源:c3p0, jdbc,druid,hikari

    Spring-jdbc:

    >实现RowMapper接口,mapRow()方法

    》转换结果集Object

    方法的封装,是通过jdbcTemplate完成。

    资源管理 ----注入数据源dataSource

    结果集处理---RowMapper---重写mapRow

    总结:

           以上工具,解决了一些问题:

    》方法封装

    》支持数据源

    》映射结果集

           没解决的问题:

    1.sql语句硬编码

    2.参数只能顺序传入【占位符的方式】

    3.没有实现实体类到数据库记录的映射

    4.没有提供缓存等功能

    2.体系结构与工作原理

    3.插件原理及spring集成

    4.手写mybatis

    1.1.2.Orm框架

    /**=======================================================================华丽的分隔线================================================*/

     1_1.7大软件设计原则

    1.1.1.开闭原则

      Open-Closed Principle【OCP原则】。

    定义

      一个软件实体,如:类,模块和函数应该对扩展开放对修改关闭

      用抽象构建框架,用实现扩展细节。

    优点:

      提高软件系统的可复用性可维护性

    它的核心思想:

      面向抽象编程,面向接口编程。

    生活实例体现:

      实行弹性工作制。8小时工作时间是固定的【对修改关闭】,而上下班时间灵活调整【对扩展开放】。

    1.1.2.依赖倒转原则

      Dependence Inversion Principle【DIP原则】。

    定义:

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

    核心思想:

      面向接口编程,不要面向实现编程。

    优点:

      可以减少类间耦合性,提高系统稳定性,提高代码可读性和可维护性。

      可降低程序所造成的风险。

     1.1.3.单一职责原则

      Simple Responsibility Principle【SRP原则】

    定义:

      不要定义多于一个导致类变更的原因。

      一个类、接口、方法只负责一项职责。

    优点:

      降低类和复杂度

      提高类的可读性

      提高系统的可维护性

      降低变更引起的风险

    1.1.4.接口隔离原则

      Interface Segregation principle[ISp原则]

    定义:

      用多个专门的接口,而不是使用一个单一的总接口。

      客户端不应该依赖它不需要的接口。

    注意:  

      一个类对应一个类的依赖,应该建立在最小的接口上

      建立单一接口,不要建立庞大臃肿的解耦

      尽量细化接口,接口中方法尽量少

    注意适度原则,一定要适度

    优点:  

      符合我们常说的高内聚,低耦合的设计思想。

      从而使得类具有良好的可读性,可扩展性和可维护性。

     1.1.5.迪米特法则

      Law of Demeter.

     

    定义:

      一个对象应该对其他对象保持最少的了解。又称最少知道原则

      尽量降低类与类之间的耦合度。【优点】

    理解:

      强调只和朋友交流,不和陌生人说话。

      

    朋友:

      出现在成员变量、方法的输入、输出参数中的类成为成员朋友类,而出现在方法体内部的类不属于朋友类。

     1.代码示例

     A.先写一个违背最少知道原则的小型系统

    【1】业务类---course课程信息
    public class Course {
    }
    【2】核心类--TeamLeader领导----给员工下命令
    public class TeamLeader {
        /**
         * 给员工发命令,检查课程总数
         */
        public void commandCheckNumber(Employee employee){
            List<Course> courses = new ArrayList<>();
            for(int i=0;i<20;i++){
                courses.add(new Course());
            }
            //员工检查课程数量
            employee.checkNumberOfCourses(courses);
        }
    }
    【3】业务类---Employee员工类
    public class Employee {
        //检查课程总数
        public void checkNumberOfCourses(List<Course> courseList){
            System.out.println("目前发布的课程数量为:"+courseList.size());
        }
    }

    [4]测试--领导安排的事情

    public class LodTest {
        public static void main(String[] args) {
            TeamLeader leader = new TeamLeader();
            Employee employee = new Employee();
            leader.commandCheckNumber(employee);
            //目前发布的课程数量为:20
        }
    }

    B.系统类图

     

    分析类图:

      TeamLeader,通过发布命令,让员工来检查课程数量,似乎是没有什么问题。

      TeamLeader其实想要的是一个结果【数量是多少】,并不关心课程Course有哪些?所以,TeamLeader不应该和Course有关联关系。

      这里违反了最少依赖原则。

      怎么来解决这个问题呢?

      我们发现,Employee与Course明显存在依赖关系。所以,可以把Leader中与Course相关代码移到Employee中。

    【1】改造代码Employee和TeamLeader

    移除TeamLeader类中Course相关代码。

    public class TeamLeader {
        /**
         * 给员工发命令,检查课程总数
         */
        public void commandCheckNumber(Employee employee){
           /* List<Course> courses = new ArrayList<>();
            for(int i=0;i<20;i++){
                courses.add(new Course());
            }*/
            //员工检查课程数量
            //employee.checkNumberOfCourses(courses);
            employee.checkNumberOfCourses();
        }
    }

    Course相关代码迁移到Employee中:

    public class Employee {
        //检查课程总数
        public void checkNumberOfCourses(){
            List<Course> courses = new ArrayList<>();
            for(int i=0;i<20;i++){
                courses.add(new Course());
            }
            System.out.println("目前发布的课程数量为:"+courses.size());
        }
    }
    【2】再次查看类图

     显然,现在的系统严格遵守最少知道原则。

     1.1.6.里氏替换原则

      Liskov Substitution Principle【LSP】。

     定义

      如果对每一个类型为T1的对象obj1,都有类型T2的对象obj2,使得以T1定义的所有程序P在所有对象obj1都替换成obj2时,

    程序P的行为没有发生变化 ,那么类型T2是类型T1的子类型。

     定义扩展:

      一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,

    子类对象能够替换父类对象,而程序逻辑不变。

     

    引申定义

      子类可以扩展父类的功能,但是不能改变父类的原有功能【就是多态的应用】

    含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

    含义2:子类中可以增加自己特有的方法【父类中没有的方法】

    含义3:当子类的方法重载父类的方法时,方法的前置条件【当方法的输入,入参】要比父类方法的输入参数更宽松。

    含义4:当子类的方法实现父类的方法时【重写,重载或实现抽象方法】,方法的后置条件【方法返回值】,要比父类更严格

    或相等。

      

     优点:

      1.约束继承泛滥,是开闭原则的一种体现。

      2.加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的可维护性,扩展性

      降低需求变更时引入风险。

    1.代码示例

    A.先写一个违反里氏替换原则的小型系统

    这里以正方形和长方形的关系,进行举例:

    【1】定义一个长方形类Rectangle
    package com.wf.design_principle.liskovsubstitution.simple;
    
    /**
     * @ClassName Rectangle
     * @Description 长方形
     * @Author wf
     * @Date 2020/4/26 13:04
     * @Version 1.0
     */
    public class Rectangle {
        private Long height;
        private long width;
    
        public Long getHeight() {
            return height;
        }
    
        public void setHeight(Long height) {
            this.height = height;
        }
    
        public long getWidth() {
            return width;
        }
    
        public void setWidth(long width) {
            this.width = width;
        }
    }
    [2]定义一个正方形Square

    因为正方形,是特殊的长方形。这里继承Retangle

    package com.wf.design_principle.liskovsubstitution.simple;
    
    /**
     * @ClassName Square
     * @Description TODO
     * @Author wf
     * @Date 2020/4/26 13:06
     * @Version 1.0
     */
    public class Square extends Rectangle {
        /** 边长 */
        private Long length;
    
        public Long getLength() {
            return length;
        }
    
        public void setLength(Long length) {
            this.length = length;
        }
        //覆盖父类的方法
    
        @Override
        public Long getHeight() {
            return this.getLength();
        }
    
        @Override
        public void setHeight(Long height) {
            this.setLength(height);
        }
    
        @Override
        public long getWidth() {
            return this.getLength();
        }
    
        @Override
        public void setWidth(long width) {
            this.setLength(width);
        }
    }
    【3】测试类
    package com.wf.design_principle.liskovsubstitution.simple;
    
    /**
     * @ClassName SimpleTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/26 13:13
     * @Version 1.0
     */
    public class SimpleTest {
        //测试长方形
       /* public static void main(String[] args) {
            Rectangle rectangle = new Rectangle();
            rectangle.setWidth(20L);
            rectangle.setHeight(10L);
            resize(rectangle);
        }*/
        //测试正方形
        public static void main(String[] args) {
            Square square = new Square();
            square.setLength(10L);
            resize(square);
        }
    
        public static void resize(Rectangle rectangle) {
            while (rectangle.getWidth() >= rectangle.getHeight()){
                rectangle.setHeight(rectangle.getHeight() + 1);
                System.out.println(""+rectangle.getWidth() +",Height:"+rectangle.getHeight());
        }
            System.out.println("Resize End ,"+rectangle.getWidth() +",Height:"+rectangle.getHeight());
        }
    }

    测试结果说明:

      正方形Square作为子类,当它同样调用父类方法时,由于while条件恒为true,导致代码陷入死循环。

      显然,这里违反里氏替换原则。    

    那么,如何解决这个问题呢?

      正方形Square和长方形Rectangle,虽然存在一定特殊关系,但它们定义为父子关系,有些不太合适。

      反而,正方形和长方形都是四边形。 这里把它们处理为兄弟关系

      

     B.改造代码
    [1].定义四边形---处理为接口
    package com.wf.design_principle.liskovsubstitution;
    
    /**
     * @ClassName QuadRectangle
     * @Description 四边形
     * @Author wf
     * @Date 2020/4/26 14:10
     * @Version 1.0
     */
    public interface QuadRectangle {
        public Long getWidth();
        public Long getHeight();
    }
    【2】定义长方形,实现四边形
    package com.wf.design_principle.liskovsubstitution;
    
    /**
     * @ClassName Rectangle
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 10:33
     * @Version 1.0
     */
    public class Rectangle implements QuadRectangle {
        private Long height;
        private Long width;
    
        public Long getHeight() {
            return height;
        }
    
        public void setHeight(Long height) {
            this.height = height;
        }
    
        public void setWidth(Long width) {
            this.width = width;
        }
    
        public Long getWidth() {
            return width;
        }
    }
    [3]定义正方形,也实现四边形接口
    package com.wf.design_principle.liskovsubstitution;
    
    /**
     * @ClassName Square
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 10:34
     * @Version 1.0
     */
    public class Square implements QuadRectangle {
        private Long length;
    
        public Long getLength() {
            return length;
        }
    
        public void setLength(Long length) {
            this.length = length;
        }
    
    
        @Override
        public Long getWidth() {
            return length;
        }
    
        @Override
        public Long getHeight() {
            return length;
        }
    }
    【4】测试类

    修改原有测试逻辑。

    revise方法,现在希望修改入参为QuadRangle[四边形]。方法直接报错了,如下所示:

    为什么会这样呢?

      因为四边形只有get方法,没有setHeight方法。  

      这样的好处是,产生编译错误,让错误提前到编译期,可以杜绝继承泛滥的作用。

      也就是说,revise方法入参不能使用四边形,只能传长方形。【因为这里本身就是长方形的特有逻辑】

      此外,正方形是不能调用revise方法的。

    最终的代码如下:

    package com.wf.design_principle.liskovsubstitution;
    
    /**
     * @ClassName LspTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 10:36
     * @Version 1.0
     */
    public class LspTest {
        //测试长方形
       /* public static void main(String[] args) {
            Rectangle rectangle = new Rectangle();
            rectangle.setWidth(20L);
            rectangle.setHeight(10L);
            resize(rectangle);
        }*/
        //测试正方形
        public static void main(String[] args) {
    //        Square square = new Square();
    //        square.setLength(10L);
    //        resize(square);
        }
    
        public static void resize(Rectangle rectangle) {
            while (rectangle.getWidth() >= rectangle.getHeight()){
                rectangle.setHeight(rectangle.getHeight() + 1);
                System.out.println(""+rectangle.getWidth() +",Height:"+rectangle.getHeight());
            }
            System.out.println("Resize End ,"+rectangle.getWidth() +",Height:"+rectangle.getHeight());
        }
    }

      这里说明的是类的继承关系,与里氏替换原则的应用。

    C.里氏替换原则应用--方法入参限定

    预期:

      子类重载父类的方法,子类方法的入参要比父类更宽松。

      如,父类方法的参数是hashMap,子类方法的入参可以是hashMap或Map

    【1】定义父类
    package com.wf.design_principle.liskovsubstitution.methodparam;
    
    import java.util.HashMap;
    
    /**
     * @ClassName Base
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 11:07
     * @Version 1.0
     */
    public class Base {
        public void method(HashMap map){
            System.out.println("父类执行");
        }
    }
    【2】定义子类

    并重载父类中方法,如下所示:

    package com.wf.design_principle.liskovsubstitution.methodparam;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @ClassName Child
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 11:10
     * @Version 1.0
     */
    public class Child extends Base {
        @Override
        public void method(HashMap map) {
            System.out.println("子类hashMap入参方法执行");
        }
        
        //重载方法
        public void method(Map map) {
            System.out.println("子类Map入参方法执行");
        }
    }
    【3】测试类
    package com.wf.design_principle.liskovsubstitution.methodparam;
    
    import java.util.HashMap;
    
    /**
     * @ClassName MethodParamTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 11:13
     * @Version 1.0
     */
    public class MethodParamTest {
        public static void main(String[] args) {
            Child child = new Child();
            HashMap hashMap = new HashMap();
            child.method(hashMap);//子类hashMap入参方法执行
            //子类实例,执行重写方法,会执行重写方法逻辑
            
            //当子类,未重写父类方法时,会执行父类方法的逻辑
            
        }
    }

    测试说明:

      父类方法传参hashMap,子类重写父类方法,并重载一个传参Map的方法。

      子类实例,传参hashMap,调用方法会执行子类重视方法的逻辑。

      

      然后,注释掉子类重写方法,如下:

    public class Child extends Base {
    //    @Override
    //    public void method(HashMap map) {
    //        System.out.println("子类hashMap入参方法执行");
    //    }
    
        //重载方法
        public void method(Map map) {
            System.out.println("子类Map入参方法执行");
        }
    }

    再执行测试方法,结果如下:

    显然,是执行父类方法的逻辑。

    现在,我们修改方法入参:

      修改目的---父类使用Map入参,子类重载时使用HashMap入参,如下所示:

    public class Base {
    //    public void method(HashMap map){
    //        System.out.println("父类执行");
    //    }
        public void method(Map map){
            System.out.println("父类执行");
        }
    }
    public class Child extends Base {
    //    @Override
    //    public void method(HashMap map) {
    //        System.out.println("子类hashMap入参方法执行");
    //    }
    
        //重载方法
        public void method(HashMap map) {
            System.out.println("子类Map入参方法执行");
        }
    }

    然后,执行测试:

    public class MethodParamTest {
        public static void main(String[] args) {
            Child child = new Child();
            HashMap hashMap = new HashMap();
            child.method(hashMap);//子类Map入参方法执行
        }
    }

    测试说明:

      这里执行了,子类重载方法的逻辑。而不是父类方法的逻辑。

    测试场景3:

      还是原来的方法定义,父类方法入参HashMap,子类方法重载入参Map。【注:重载方法注释掉】

      然后,修改测试类实例创建,如下所示:

    public class MethodParamTest {
        public static void main(String[] args) {
            Base child = new Child();       //声明父类,new 子类
            HashMap hashMap = new HashMap();
            child.method(hashMap);//父类执行
        }
    }

    测试结果说明:

      无论创建实例时,声明父类new子类,还是创建子类补全,都是执行父类方法的逻辑。

      这里说明的是,改变实例声明类型,不会影响最终执行结果。【这也是里氏替换原则的体现】

    D.里氏替换原则应用--方法返回值限定

    预期:

      子类重载父类的方法,子类方法的返回值类型要相比父类更严格或相等。

      如,父类方法的返回是Map,子类方法的返回值可以是hashMap或Map或LinkedMap

    【1】定义父类方法
    public abstract class Base {
        public abstract Map method();
    }

    方法返回Map类型。

    [2]定义子类
    package com.wf.design_principle.methodreturn;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @ClassName Child
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 11:50
     * @Version 1.0
     */
    public class Child extends Base {
        @Override
        public Map method() {
            HashMap hashMap = new HashMap();
            System.out.println("执行子类的method");
            hashMap.put("msg","子类method");
            return hashMap;
        }
    }
    【3】测试类
    package com.wf.design_principle.methodreturn;
    
    /**
     * @ClassName MethodReturnTest
     * @Description TODO
     * @Author wf
     * @Date 2020/4/27 13:49
     * @Version 1.0
     */
    public class MethodReturnTest {
        public static void main(String[] args) {
            Base child = new Child();
            System.out.println(child.method());
        }
    }

    假如我们想改变,子类方法的返回值,以更为宽松的类型进行返回,结果如下:

    因为,父类方法中返回值为Map类型。现在想修改为返回Object类型,明显,代码会提示编译报错。

    显然,这里也体现的里氏替换原则。

     1.1.7.合成复用原则

      Composite & Aggregate Reuse Principle【C&ARP】

      也叫组合复用原则

    定义

      尽量使用对象组合、聚合,而不是继承关系来达到软件复用的目的。

    聚合has-a和组合contains-a

    优点:  

      可以使系统更加灵活,降低类与类之间的耦合度

      一类的变化对其他类造成影响相对较小。

    何时使用合成/聚合,何时使用继承?

    聚合has-a,组合contains-a,继承is-a

      继承关系,关联性很强,如:狗是一种动物,使用狗---继承---动物

      组合关系,整体与部分的关系。 如:人的身体构成----头,手,脚

    1.1.7.1.代码示例

    这里以数据库连接,Connection与Dao之间的关系,来说明组合复用原则。

    1.定义Connection类
    package com.wf.design_principle.compositereuse;
    
    /**
     * @ClassName DBConnection
     * @Description 数据库连接对象
     * @Author wf
     * @Date 2020/4/27 14:15
     * @Version 1.0
     */
    public class DBConnection {
        public String getConnection(){
            return "获取Mysql数据库连接";
        }
    }

    注意:在实战编程中,需要满足开闭原则,面向接口编程。【这里为简单,定义为实现类】

    2.dao接口定义
    package com.wf.design_principle.compositereuse;
    
    /**
     * @ClassName ProductDao
     * @Description 数据库表操作
     * @Author wf
     * @Date 2020/4/27 14:16
     * @Version 1.0
     */
    public class ProductDao {
        private DBConnection dbConnection;
        public void setConnection(DBConnection dbConnection){
            this.dbConnection = dbConnection;
        }
    
        public void addProduct(){
            String conn = dbConnection.getConnection();
            System.out.println("获得数据库连接");
        }
    }

    可以发现,ProductDao中引用DBConnection,它们之间就是一种组合复用关系。

    附录:

    1.Maven 自定义archtype

    目的:

           我发现idea 创建maven web 项目,项目结构不完整。我想自定义一个archtype.

    1.1. 通过archtype-webapp创建web项目,命名为archtype-wf-web

    1.2. 然后补全项目结构,添加java/resources,test/main/java,test/main/resources

    1.3. 修改pom,在pom.xml中添加archtype插件。如下所示:

     

     1.4. 自定义archtype创建完成。

    1.5. 然后在插件项目所在目录,执行命令mvn archetype:create-from-project

    1.6. 会在项目下生成target目录,生成archetype插件。如下所示:

     

    1.7.然后把archetype安装在本地【添加到maven仓库】,使用mvn install命令:

    然后,使用自定义archtype创建项目。有两种方式:

    》mvn命令行方式

    》idea导入自定义archtype,通过它创建。

    https://blog.csdn.net/qq_32331997/article/details/76177819

    2.mvn命令行方式创建项目

    1.进入自定义arctype目录,使用cmd

    2.使用命令:mvn archetype:generate -DarchetypeCatalog=local

    3.idea生成类图技巧

      对于多个关联要想一次性生成类图,先在包下选中所有类。如下所示:

    然后,使用快捷键,ctrl+Alt+shift+U,这时生成的是没有关联关系的类图,如下所示:

    然后,点击关联关系图标,生成类图如下所示:

  • 相关阅读:
    mac c++编译出现segmentation fault :11错误
    ssh 连接缓慢解决方法
    237. Delete Node in a Linked List
    203. Remove Linked List Elements
    Inversion of Control Containers and the Dependency Injection pattern
    82. Remove Duplicates from Sorted List II
    83. Remove Duplicates from Sorted List
    SxsTrace
    使用CCleaner卸载chrome
    decimal and double ToString problem
  • 原文地址:https://www.cnblogs.com/wfdespace/p/12671607.html
Copyright © 2011-2022 走看看