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

    软件开发七大原则

    一、开闭原则:面对扩展开放,面对修改关闭

    (1)指的是在开发的过程中尽量的去扩展代码,而不是去修改原来的代码,以免影响到之前的逻辑。

    (2)强调的是用抽象构建框架,用实现扩展细节。

    (3)可以提高软件系统的可复用性及可维护性

    (4)例:原有课程类,闲杂要对课程进行打折,应该怎么处理呢?
    原有课程类:

    package test1;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public interface Course {
        public String getId();
        public String getName();
        public double getPrice();
    }
    package test1;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class JavaCourse implements Course {
      private String id;
      private String name;
      private double price;
    
        @Override
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public double getPrice() {
            return price;
        }
    
        public void setPrice(double price) {
            this.price = price;
        }
    
        public JavaCourse(String id, String name, double price) {
            this.id = id;
            this.name = name;
            this.price = price;
        }
    }

     新增打折课程类

    package test1;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class JavaDiscountCourse extends JavaCourse{
    
        public JavaDiscountCourse(String id, String name, double price) {
            super(id, name, price);
        }
    
        public double getOriginPrice() {
            return super.getPrice();
        }
    
        public double getPrice() {
            return super.getPrice()*0.8;
        }
    
    }

    在不修改原来的逻辑的基础上作出对应的修改

    测试类:

    package test1;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Test {
        public static void main(String[] args) {
            JavaDiscountCourse cource = new JavaDiscountCourse("001","java",100.0);
            System.out.println(cource.getPrice());
            System.out.println(cource.getOriginPrice());
        }
    }

     二、依赖倒置原则:依赖于抽象接口,不要依赖于具体实现。

    (1)要求对抽象进行编程,不要对实现进行编程。

    (2)降低了客户与实现模块间的耦合。

    (3)例:原有转换工具类可以转换word,pdf两种类型的文件,现在该工具类需要扩展使其在原来的基础上还能转换excel文件

    如果是按照依赖实现编程:

    package test2;
    
    /**
     * des: office文档转pdf类
     * author:songyan
     * date: 2019/10/6
     **/
    public class Trans {
        public void transWord(){
            System.out.println("转换");
        }
        public void transPDF(){
            System.out.println("pdf转换");
        }
    }

    客户端:

    package test2;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Client {
        public static void main(String[] args) {
            Trans trans = new Trans();
            trans.transWord();
            trans.transPDF();
        }
    }

    使用这种方式的弊端就在于,如果说要扩展转换工具类的工具类的功能,就需要去修改之前的代码,显然这种做法是非常不安全的,有可能就会影响之前代码。

    更好的做法是:

    package test2;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class TransUtil {
        public void trans(OfficeDocument document){
            document.trans();
        }
    }
    package test2;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class WordDocument implements OfficeDocument {
        @Override
        public void trans() {
            System.out.println("word文档转换");
        }
    }
    package test2;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class PDFDocuemnt implements OfficeDocument{
    
        @Override
        public void trans() {
            System.out.println("pdf转换");
        }
    }
    package test2;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Test {
        public static void main(String[] args) {
            TransUtil trnas = new TransUtil();
            trnas.trans(new WordDocument());
            trnas.trans(new PDFDocuemnt());
    
        }
    }

    在转换工具类中是针对处理对象的接口进行处理的,在想要扩展功能的时候只需要添加一个实现类即可,例如:

    package test2;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class ExcelDocument implements OfficeDocument{
    
        @Override
        public void trans() {
            System.out.println("excel转换");
        }
    }
    package test2;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Test {
        public static void main(String[] args) {
            TransUtil trnas = new TransUtil();
            trnas.trans(new WordDocument());
            trnas.trans(new PDFDocuemnt());
            trnas.trans(new ExcelDocument());
    
        }
    }

    使用这种方法只需要扩展之前的代码,而不需要修改之前的代码,其实也就是上面说开闭原则。

    这个例子其实就是“依赖注入”,那么依赖注入的方式又包括构造器注入,setter方法注入,下面简介一下这两种方式

    1)构造器注入

    package test2.generator;
    
    import test2.OfficeDocument;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class TransUtil {
        private OfficeDocument officeDocument;
    
        public TransUtil(OfficeDocument officeDocument) {
            this.officeDocument = officeDocument;
        }
    
        public void trans() {
            officeDocument.trans();
        }
    }
    package test2.generator;
    
    import test2.WordDocument;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Test {
        public static void main(String[] args) {
            TransUtil transUtil = new TransUtil(new WordDocument());
            transUtil.trans();
        }
    }

    2)setter方式注入

    package test2.setter;
    
    import test2.OfficeDocument;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class TransUtil {
        private OfficeDocument officeDocument;
    
        public void setOfficeDocument(OfficeDocument officeDocument) {
            this.officeDocument = officeDocument;
        }
    
        public void trans(){
            officeDocument.trans();
        }
    }
    package test2.setter;
    
    import test2.WordDocument;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Test {
        public static void main(String[] args) {
            TransUtil  transUtil= new TransUtil();
            transUtil.setOfficeDocument(new WordDocument());
            transUtil.trans();
        }
    }

    以抽象为基准比以细节为基准搭建起来的框架要稳健的多,因此,大家在拿到需求之后要面向抽象接口来编程,先顶层在底层来设计代码结构。

    三、单一职责原则:一个类,一个接口,一个方法应该只有一个职能

    (1)如果有多个职能,则其中一个职能发生改变之后,就需要修改这个类的功能,就有可能影响到另一个职能。所以我们有必要对他们进行一定程度的拆分。

    (2)降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险

    (3)例:有一个对nginx操作的工具类如下:

    package test3;
    
    /**
     * Nginx工具类
     * author:songyan
     * date: 2019/10/6
     **/
    public class NginxUtil {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void start(){
            System.out.println("启动nginx");
        }
    }

    里面有设计nginx基本信息的方法,也有启动nginx的方法,比如说,nginx的启动方法发生了改变,这个时候就需要修改nginxUtil类,那再修改的过程中就有可能对他的其他方法产生影响,因此我们可以对他进行以下划分,将他隔离开:

    package test3;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class NginxInfo {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    package test3;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class NginxOperator {
        public void start(){
            System.out.println("nginx启动");
        }
    }

    接口与此类似。。

    方法不符合单一职责而原则的例子:

        public void modefied(String name,String fav){
            System.out.println("修改名字");
            System.out.println("修改爱好");
        }

    方法符合单一职责而原则的例子:

        public void modefiedName(String name){
            System.out.println("修改姓名");
        }
        public void modefiedFav(String fav){
            System.out.println("修改爱好");
        }

    其实这种方式乍一看可能感觉不到这样写的好处在哪里,我突然想到我最近的一个任务,是修改之前的同事写的一个系统,里面大多数的逻辑代码都是摞在一起的,一个方法中有几百行代码,做什么的都有,可能有的时候只需要改其中的一个点,但是可能就会影响到其他部分的代码,还有一个缺点就是,里面的代码错综复杂,可能的要找到你想改的地方都很难。

    总结一下就是,在写代码的过程中如果尽量的保持单一指责原则就会,提高类的可读性,提高系统的可维护性,降低变更引起的风险。

    四、接口隔离原则:使用多个专一的接口,而不使用单一的总接口

    (1)一个类对一个类的接口应该建立在最小的接口之上。

    (2)尽力单一的接口,而不要建立单一臃肿的接口

    (3)尽量的细化接口,接口中的方法尽量少(不是越少越好,要适度)

    (4)例:前几天总结的office文档转pdf的方式,其中openoffice,aspose是支持windows,linux两种系统的,但是jacob只支持windows系统,下面的代码就会存在一定的问题。

    在下面的代码中,将windows转换的方式,Linux转换的方式放在了一个接口中:

    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public interface Itrans {
        void windowsTrans();
        void linuxTrans();
    
    }

    在openoffice,aspose两种方式中,是没有问题的,见下:

    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class AsposeTrans implements  Itrans{
        @Override
        public void windowsTrans() {
            System.out.println("Aspose在windows的转换");
    
        }
    
        @Override
        public void linuxTrans() {
            System.out.println("Aspose在linux的转换");
    
        }
    }
    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class OpenofficeTrans implements Itrans{
        @Override
        public void windowsTrans() {
            System.out.println("oppenoffice在windows的转换");
        }
    
        @Override
        public void linuxTrans() {
            System.out.println("oppenoffice在linux的转换");
        }
    }

    但是,你会发现在jacob中,他是不支持在Linux的转换的,但是实现这个接口的话必须重写这个接口。。

    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class JacobTrans implements Itrans{
        @Override
        public void windowsTrans() {
            System.out.println("Jacob在windows的转换");
        }
    
        @Override
        public void linuxTrans() {
    
        }
    }

    针对上面的情况可以做以下完善,将接口中的方法拆分到两个接口中:

    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public interface IWindowsTrans {
        void windowsTrans();
    }
    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public interface ILinuxTrans {
        void linuxTrans();
    }

    对两种系统都支持的方式可以去实现两种接口

    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class AsposeTrans implements  IWindowsTrans,ILinuxTrans{
        @Override
        public void windowsTrans() {
            System.out.println("Aspose在windows的转换");
    
        }
    
        @Override
        public void linuxTrans() {
            System.out.println("Aspose在linux的转换");
    
        }
    }

    对只支持一种系统的方式只需要实现一种接口

    package test4;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class JacobTrans implements  IWindowsTrans{
        @Override
        public void windowsTrans() {
            System.out.println("Jacob在windows的转换");
        }
        
    }

    五、迪米特法则:最少知道原则。

    (1)迪米特原则主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类,而出现在方法体内部的类不属于朋友类。

    (2)例:老板让leader查询课程的数量

    package test5;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Boss {
        public void askClassNumber(){
            List<Clazz> clazzLiat = new ArrayList<Clazz>();
            clazzLiat.add(new Clazz());
            clazzLiat.add(new Clazz());
            clazzLiat.add(new Clazz());
            TeamLeader teamLeader = new TeamLeader();
            System.out.println(teamLeader.getClassNumber(clazzLiat));
        }
    }
    package test5;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class TeamLeader {
        public int getClassNumber(List<Clazz> clazzLiat) {
            return clazzLiat.size();
        }
    }
    package test5;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Clazz {
    }

    以上代码实现所要求的功能是完全没有问题的,但是,根据迪米特法则,Boss类是没有必要跟Clazz类关联的,所以可以改成以下代码

    package test5;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Boss {
        public void askClassNumber(){
    
            TeamLeader teamLeader = new TeamLeader();
            System.out.println(teamLeader.getClassNumber());
        }
    }
    package test5;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class TeamLeader {
        public int getClassNumber() {
            List<Clazz> clazzLiat = new ArrayList<Clazz>();
            clazzLiat.add(new Clazz());
            clazzLiat.add(new Clazz());
            clazzLiat.add(new Clazz());
            return clazzLiat.size();
        }
    }
    package test5;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Clazz {
    }
    package test5;
    
    /**
     * author:songyan
     * date: 2019/10/6
     **/
    public class Test {
        public static void main(String[] args) {
            Boss boss = new Boss();
            boss.askClassNumber();
        }
    }

    简单说就是boss只需要去问leader课程数量有多少,具体的统计过程需要leader自己来完成,最终把结果给BOSS即可。

    六、里氏替换原则:父类可以使用的地方也可以适用子类

    (1)例:在开闭原则的例子中,在打折类中使用了getPrice获取了打折后的价格,使用getOriginPrice获取打折之前的价格,这里其实是违背了里氏替换原则的,根据里氏替换原则,JavaCourse可以通过getPrice获取课程的价格(原价),那么其子类应该也是可以通过该方法获取课程原价的,但是在重写的时候逻辑却换成了获取折后价。

    那么正确的写法应该如下:

    package test6;
    
    /**
     * author:songyan
     * date: 2019/10/7
     **/
    public interface ICourse {
        public String getId();
        public String getName();
        public double getPrice();
    }
    package test6;
    
    /**
     * author:songyan
     * date: 2019/10/7
     **/
    public class JavaCourse implements ICourse{
        private String id;
        private String name;
        private double price;
    
        public double getPrice() {
            return price;
        }
    
        public void setPrice(double price) {
            this.price = price;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public JavaCourse(String id, String name, double price) {
            this.id = id;
            this.name = name;
            this.price = price;
        }
    }
    package test6;
    
    /**
     * author:songyan
     * date: 2019/10/7
     **/
    public class DiscountJavaCourse extends JavaCourse{
        public  double getPrice(){
            return super.getPrice();
        }
    
        public double getDiscountPrice(){
               return super.getPrice()*0.8;
        }
        public DiscountJavaCourse(String id, String name, double price) {
            super(id, name, price);
        }
    }

    七、合成复用原则:尽量使用组合,聚合的方式而不是继承的方式实现复用

    (1)合成服用原则具体指的是在创建新的对象的时候使用已有的对象。

    (2)使用的方式有两种:组合/聚合,继承。

    (3)在新对象与“已有的对象”两者的关系是“is-a”时,使用继承关系;两者的关系是"has-a"时,使用组合/聚合的方式。

    (4)在使用合成服用原则时,注意不要滥用继承关系

    (5)区分“组合”,“聚合”,“继承”

      1)组合(整体与部分,同生命周期)

       新旧对象是“has-a”的关系,是一种“强”的拥有关系,原有的对象是新对象的一部分,并且两个对象的生命周期是一样的。

       例:人与四肢的关系。人有四肢,四肢是人体的一部分,人没了,四肢也就不存在了。

      2)聚合(整体与部分,不同生命周期)

        新旧对象是"has-a"的关系,是一种“弱”的拥有关系,原有的对象不是新对象的一部分,两个对象的生命周期可以不相同。

        例:人与人群的关系。人群里面有人,当人群散了,人照样可以存在。

      3)继承

        例:学生与人的关系。学生是人,输入人类的一种。

       新旧对象是“is-a”的关系。

  • 相关阅读:
    学生管理系统报错(一)
    POJ3264 Balanced Lineup
    MySQL主从复制和读写分离
    身边的同事辞职去旅行
    怎样查看eclipse是32位还是64位
    Mule ESB-3.Build a webservice proxy
    《Head First 设计模式》学习笔记——复合模式
    DecimalFormat格式化输出带小数的数字类型
    黑马day01 笔记
    [Swift]LeetCode835. 图像重叠 | Image Overlap
  • 原文地址:https://www.cnblogs.com/excellencesy/p/11627841.html
Copyright © 2011-2022 走看看