设计模式—设计原则

1.单一原则

        一个类或者模块只负责完成一个职责(或者功能),也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。换个角度来讲就是,一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。

public class UserInfo {
    private long userId;
    private String username;
    private String email;
    private String telephone;
    private String provinceOfAddress; // 省
    private String cityOfAddress; // 市
    private String regionOfAddress; // 区
    private String detailedAddress; // 详细地址
}

       上述的UserInfo是用来记录用户信息的类,从其中的信息看都是和用户相关的,是满足单一原则,但是其中的地址信息可以拆分出来作为单独的AddressInfo,UserInfo 只保留除 AddressInfo之外的其他信息,拆分之后的两个类的职责更加单一。实际开发中到底要不要拆分,我们应该从具体的应用场景去考量,例如微信中的地址信息,它只是用来展示,那么放在一起就是合理的,如果在淘宝中,因为地址信息还会用在物流中,那么就应该拆分出来作为单独的信息。

       不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的。在某种应用场景或者当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果换个应用场景或着在未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。

      看一个类的职责是否足够单一,并没有一个非常明确的、可以量化的标准,可以说,这是一个主观的认知。实际开发中,我们可以先不拆分写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。满足单一原则。

    到底要不要拆分可以从以下几点考量:、

    1.类是否过大,主要是代码行数、函数或属性过多

       类过大,会影响可读性和可维护性,这时候我们就应该考虑拆分,类的有效代码行数(注释空行除外)一般保持在300行左右,属性方法保持在10个以内比较合理

     2.这个类依赖其它类,或者其它类依赖这个类过多

       依赖或者被依赖过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分

    单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。但是如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

2.对扩展开放、修改关闭

     详细表述一下,那就是,添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。

public class StudentReport {

    private String name;
    private Integer englishScore;  //英语成绩
    private Integer mathematicsScore;  //数学成绩

    public StudentReport(String name, Integer englishScore, Integer mathematicsScore) {
        this.name = name;
        this.englishScore = englishScore;
        this.mathematicsScore = mathematicsScore;
    }

    public String getGrade() {
        String level = null;
        if (englishScore >= 90 && mathematicsScore >= 90) {
            level = "优秀";
        } else if (englishScore >= 70 && mathematicsScore >= 70) {
            level = "良好";
        } else if (englishScore >= 60 && mathematicsScore >= 60) {
            level = "及格";
        } else if (englishScore < 60 || mathematicsScore < 60) {
            level = "不及格";
        }
        return level;
    }
}

上面这段代码非常简单,是一个学生成绩单包括英语和数学成绩,逻辑主要在getGrade,获取成绩等级,如果我们现在要将语文成绩也纳入成绩单,我们需要增加一个属性,并且修改getGrade逻辑

public class StudentReport {

    private String name;
    private Integer englishScore;
    private Integer mathematicsScore;
    private Integer languageScpre;

    public String getGrade() {
        String level = null;
        if (englishScore >= 90 && mathematicsScore >= 90 && languageScpre>=90) {
            level = "优秀";
        } else if (englishScore >= 70 && mathematicsScore >= 70 && languageScpre>=70) {
            level = "良好";
        } else if (englishScore >= 60 && mathematicsScore >= 60 && languageScpre>=60) {
            level = "及格";
        } else if (englishScore < 60 || mathematicsScore < 60 ||languageScpre<60) {
            level = "不及格";
        }
        return level;
    }
}

这样的代码修改实际上存在挺多问题的。一方面,我们对接口进行了修改,这就意味着调用这个接口的代码都要做相应的修改。另一方面,修改了 check() 函数,相应的单元测试都需要修改,这就不符合对扩展开放,对修改关闭原则,我们对代码就行重构。

public class GradeInfo {
    private String name;
    private Integer englishScore;
    private Integer mathematicsScore;
    private Integer languageScpre;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getEnglishScore() {
        return englishScore;
    }

    public void setEnglishScore(Integer englishScore) {
        this.englishScore = englishScore;
    }

    public Integer getMathematicsScore() {
        return mathematicsScore;
    }

    public void setMathematicsScore(Integer mathematicsScore) {
        this.mathematicsScore = mathematicsScore;
    }

    public Integer getLanguageScpre() {
        return languageScpre;
    }

    public void setLanguageScpre(Integer languageScpre) {
        this.languageScpre = languageScpre;
    }
}
public interface GradleHandle {
    public String check(GradeInfo info);
}

public class ExcellentHandler implements GradleHandle{
    @Override
    public String check(GradeInfo info) {
        if(info.getEnglishScore()>90 && info.getLanguageScpre()>90 && info.getMathematicsScore()>90) {
            return "优秀";
        }
        return null;
    }
}

public class GoodHandle implements GradleHandle{
    @Override
    public String check(GradeInfo info) {
        if(info.getEnglishScore()>70 && info.getLanguageScpre()>70 && info.getMathematicsScore()>70) {
            return "良好";
        }
        return null;
    }
}
public class Grade {
    private List<GradleHandle> gradleHandles = new ArrayList<GradleHandle>();

    public void addGradleHandle(GradleHandle gradleHandle) {
        this.gradleHandles.add(gradleHandle);
    }

    public String grade(GradeInfo info) {
        for (GradleHandle handler : gradleHandles) {
            String check = handler.check(info);
            if(check!=null && !check.isEmpty()) {
                return check;
            }
        }
        return null;
    }
}

重构之后的代码更加灵活和易扩展。

3.里式替换原则

派生类(子类)对象可以在程序中代替其基类(超类)对象

里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。举例说明:

public class User {
    private Integer id;

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class UserHandle {

    public void save(String name ,Integer id) {
        User user = new User();
        if(name!=null && !name.isEmpty()) {
            user.setName(name);
        }
        if(id!=null) {
            user.setId(id);
        }
        System.out.println("save user");
    }
}

public class UserHandleSave extends  UserHandle{
    public void save(String name ,Integer id) {
        User user = new User();
        if(name!=null && !name.isEmpty()) {
            user.setName(name);
        }
        if(id!=null) {
           throw new RuntimeException();
        }
        System.out.println("save user");
    }

    public static void main(String[] args) {
        UserHandle uh = new UserHandleSave();
        uh.save("xxx",1);
    }
}

上述就不符合里式替换原则,违背了父类对异常的约定

4.接口隔离原则

       客户端不应该依赖它不需要的接口。另一种定义是:类间的依赖关系应该建立在最小的接口上。接口隔离原则将非常庞大、臃肿的接口拆分成更小具体的接口,这样客户讲会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署。

      假如有一个用户系统,用户信息只允许后台员修改,其他人员没有权限修改,我们接口设计如下:


public interface UpdateUser {

    public void save(User user);

    public void update(User user);
}

public interface ViewUser {

    public User get(Integer id);

    public List<User> query(String name);
}

public class UserService implements UpdateUser,ViewUser {
    @Override
    public void save(User user) {

    }

    @Override
    public void update(User user) {

    }

    @Override
    public User get(Integer id) {
        return null;
    }

    @Override
    public List<User> query(String name) {
        return null;
    }
}

如果有部分调用者只需要接口中的部分功能,那我们就需要把接口拆分成粒度更细的多个即接口,让调用者只依赖它需要的那个细粒度接口

5.依赖倒置原则

在面向对象领域中,依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。

该原则规定:

  1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
  2. 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

该原则颠倒了一部分人对于面向对象设计的认识方式。如高层次和低层次对象都应该依赖于相同的抽象接口

public interface MessageSender {
    public void send(String msg);
}

public class MessageSenderImpl implements MessageSender{
    @Override
    public void send(String msg) {
        System.out.println("send msg");
    }
}

public class Notification {

    MessageSender messageSender;

    public void setMessageSender(MessageSender messageSender) {
        this.messageSender = messageSender;
    }

    public void send() {
        messageSender.send("xxx");
    }

    public static void main(String[] args) {
        Notification notification = new Notification();
        MessageSender messageSender = new MessageSenderImpl();
        notification.setMessageSender(messageSender);
        notification.send();
    }
}

6.迪米特法则

每个模块(unit)只应该了解那些与它关系密切的模块(units: only units “closely” related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”

通俗的说就是不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中的“有限知识”)。案例如下:

public interface Serializable {
    String serialize(Object object);
}

public interface Deserializable {
    Object deserialize(String text);
}

public class Serialization implements Serializable, Deserializable {

    @Override
    public String serialize(Object object) {
        String serializedResult = "";
        return serializedResult;
    }

    @Override
    public Object deserialize(String str) {
        Object deserializedResult = "...";
        return deserializedResult;
    }

}

public class Demo1 {
    Serializable serializable;

    public Demo1(Serializable serializable) {
        this.serializable = serializable;
    }
}

public class Demo2 {
    private Deserializable deserializer;

    public Demo2(Deserializable deserializer) {
        this.deserializer = deserializer;
    }
}

高内聚、松耦合”是一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。所谓松耦合指的是,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。

不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值