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,这时生成的是没有关联关系的类图,如下所示:

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

  • 相关阅读:
    SSM——事务配置
    SSM——Spring+Mybtis整合(代理【mapper】开发模式)
    objective-c(五)关于代码块的使用
    objective-c(四)内存管理
    objective-c(三)类与对象的方法调用
    objective-c(二)基本数据类型介绍
    objective-c(一)关于基本数据类型打印输出方式
    Eclipse启动发生的错误:An internal error occurred during: "Initializing Java Tooling".
    单例模式
    Java 代理模式
  • 原文地址:https://www.cnblogs.com/wfdespace/p/12671607.html
Copyright © 2011-2022 走看看