软件设计七大原则

设计原则:

在软件设计中应该遵循的原则,这样比较容易设计出易扩展、易维护、可重用、灵活性高的软件架构。设计原则是设计模式的基础,每个设计模式或多或少都会遵循一个以上的设计原则。

单一职责原则:

对一个类或者方法来说,只负责一项职责,如果一个类负责多个职责的话,某个职责的修改可能会导致令一个职责错误,所以要将职责分离开来。可以在类级别或者方法级别上遵守单一职责原则。

UML图及代码:
改进前:
在这里插入图片描述

/**
 * 交通工具
 */
public class Vehicle {

    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        System.out.println(vehicle.run("汽车"));
        System.out.println(vehicle.run("火车"));
        System.out.println(vehicle.run("飞机"));
    }

    public String run(String name){
        return name + "在公路上行驶";
    }
}

//飞机在公路上行驶明显是不符合逻辑的,该代码就没有遵循单一职责原则,因为在天空中的交通工具跟在公路上的交通工具的职责是不一样的,所以要分离开来。

改进后:
在这里插入图片描述

public class RoadVehicle {

    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        SkyVehicle skyVehicle = new SkyVehicle();
        System.out.println(roadVehicle.run("汽车"));
        System.out.println(roadVehicle.run("火车"));
        System.out.println(skyVehicle.run("飞机"));
    }

    public String run(String name){
        return name + "在公路上行驶";
    }
}

public class SkyVehicle {
    public String run(String name){
        return name + "在天空中上飞行";
    }
}

//把职责分离开来,天空的交通工具只负责天空飞行的职责,公路上的交通工具只负责在公路上行驶的职责。

单一职责的优点和细节:

  1. 可以降低类的复杂度,因为一个类只负责一项职责,类也相对简单了。
  2. 提高了维护性和可读性。
  3. 降低变更引起的风险。
  4. 通常情况下,我们应该遵守单一职责原则,只有在代码逻辑足够简单的情况下才能违反单一职责原则。只有类中的方法足够少,才能在方法层面违反该原则。
接口隔离原则:

客户端不应该依赖于他不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。例如:A依赖于B,B实现了接口C,A类就要用到C接口的所有方法。

uml图及代码:
在这里插入图片描述

public interface A {
    void operator1();
    void operator2();
    void operator3();
    void operator4();
    void operator5();
}

public class B implements A {
    @Override
    public void operator1() {
        System.out.println("B实现A-->operator1()");
    }

    @Override
    public void operator2() {
        System.out.println("B实现A-->operator2()");
    }

    @Override
    public void operator3() {
        System.out.println("B实现A-->operator3()");
    }

    @Override
    public void operator4() {
        System.out.println("B实现A-->operator4()");
    }

    @Override
    public void operator5() {
        System.out.println("B实现A-->operator5()");
    }
}
public class C implements A {
    @Override
    public void operator1() {
        System.out.println("C实现A-->operator1()");
    }

    @Override
    public void operator2() {
        System.out.println("C实现A-->operator2()");
    }

    @Override
    public void operator3() {
        System.out.println("C实现A-->operator3()");
    }

    @Override
    public void operator4() {
        System.out.println("C实现A-->operator4()");
    }

    @Override
    public void operator5() {
        System.out.println("C实现A-->operator5()");
    }
}

public class D {

    public void useOperator1(C c){
        c.operator1();
    }

    public void useOperator2(C c){
        c.operator2();
    }

    public void useOperator3(C c){
        c.operator3();
    }
}

public class E {

    public void useOperator1(B b){
        b.operator1();
    }

    public void useOperator4(B b){
        b.operator4();
    }

    public void useOperator5(B b){
        b.operator5();
    }
}

//这个是违反了接口隔离原则的,因为类D依赖了类C却只使用了接口A的其中三个方法而已,类E依赖了类B却只使用了接口A的其中三个方法而已。

改进后:
在这里插入图片描述

public interface A {

    void operator1();
}

public interface B {
    void operator2();
    void operator3();
}

public interface C {
    void operator4();
    void operator5();
}

public class D implements A,B {
    @Override
    public void operator1() {
        System.out.println("D 实现 A-->operator1()");
    }

    @Override
    public void operator2() {
        System.out.println("D 实现 B-->operator2()");
    }

    @Override
    public void operator3() {
        System.out.println("D 实现 B-->operator3()");
    }
}

public class E implements A,C{

    @Override
    public void operator1() {
        System.out.println("E 实现 A-->operator1()");
    }

    @Override
    public void operator4() {
        System.out.println("E 实现 C-->operator4()");
    }

    @Override
    public void operator5() {
        System.out.println("E 实现 C-->operator5()");
    }
}

public class F {
    public void useOperator1(D d){
        d.operator1();
    }

    public void useOperator2(D d){
        d.operator2();
    }

    public void useOperator3(D d){
        d.operator3();
    }
}

public class G {

    public void useOperator1(E e){
        e.operator1();
    }

    public void useOperator4(E e){
        e.operator4();
    }

    public void useOperator5(E e){
        e.operator5();
    }
}

//将本来的接口A拆分成了A、B、C ,然后D类实现接口A、B,E类实现接口A、C,这样就达到了接口隔离的原则。

依赖倒置原则:

  1. 高层模块(调用方)不应该依赖低层模块(提供方),二者都应该依赖其抽象。
  2. 抽象不应该依赖细节,细节应该依赖抽象。依赖传递都传递抽象,接口或者抽象类。
  3. 依赖倒置的中心思想是面向接口编程。充分利用多态。
  4. 依赖倒置原则基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定得多,在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
  5. 使用接口或者抽象类的目的是制定好规范,而不涉及任何具体操作,把展现细节的任务交给他们的实现类去完成。

依赖传递的三种方法:

  1. 接口。
  2. setter方法。
  3. 构造器。
    UML图及代码:
    改进前:
    在这里插入图片描述
public class RoadVehicle {

	private String name;
	public void setName(String name){
		this.name = name;
	}
    public void run(){
        System.out.println(name + "在公路上行驶");
    }
}


public class SkyVehicle {
	private String name;
	public void setName(String name){
		this.name = name;
	}
    public void run(){
        System.out.println(name + "在天空中飞行");
    }
}

public class TestVehicle {

    public void runRoadVehicle(RoadVehicle roadVehicle){
        roadVehicle.run();
    }

    public void runSkyVehicle(SkyVehicle skyVehicle){
        skyVehicle.run();
    }
}

//分析:TestVehicle 类是高层模块(调用方),依赖了SkyVehicle、RoadVehicle底层模块,而没有依赖其抽象,违反了依赖倒置原则,所以如果每次增加一个新的交通工具TestVehicle 类就要新增一个方法。

改进后:
在这里插入图片描述



public interface Vehicle {
    void run();
}

public class RoadVehicle implements Vehicle{

    private String name;

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

    @Override
    public void run(){
        System.out.println(name + "在公路上行驶");
    }
}

public class SkyVehicle implements Vehicle{
    private String name;

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

    @Override
    public void run(){
        System.out.println(name + "在天空中飞行");
    }
}

public class TestVehicle {

    public void runVehicle(Vehicle Vehicle){
        Vehicle.run();
    }

    public static void main(String[] args) {
        TestVehicle testVehicle = new TestVehicle();
        testVehicle.runVehicle(new RoadVehicle());
        testVehicle.runVehicle(new SkyVehicle());
    }

}

//分析:改进后,RoadVehicle 、SkyVehicle 会实现Vehicle接口,然后TestVehicle 没有依赖低层模块,而是依赖其抽象,这样,每当新增一个交通工具时,高层接口无需改变。


依赖倒置原则的注意事项和细节:

  1. 底层模块尽量都要有抽象父类或接口。或者两者都有。
  2. 变量的声明一般都用抽象父类或者接口,然后将引用指向实际的实现类(多态),这样引用变量和时间对象之间会有一个缓冲层,利于程序扩展和优化。
  3. 继承时遵循里氏替换原则。

里氏替换原则:

面向对象中的继承性的思考和说明:

  1. 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然他不强制要求所有子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
  2. 继承在给程序设计带来便利的同时,也会带来弊端,比如继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他类所继承,则当这个类需要修改时,必须考虑到所有子类,并且父类修改后,所有涉及到子类的功能都有可能故障。
  3. 而里氏替换原则是对继承的规范和约束。

里氏替换原则定义:有类A和B,B继承A,OA是A的对象,OB是B的对象,一个程序P调用OA对象的所有方法,如果换成调用OB对象的这些方法的话,对这个程序P的行为没有任何影响,则符合里氏替换原则。也就是说子类不应该重写父类已实现的方法,调用父类的方法与调用子类的相应方法所产生的行为一致。

UML图及代码:
改进前:
在这里插入图片描述

public class A {
    /**
     * 加法
     * @param member1
     * @param member2
     * @return
     */
    public int fun(int member1,int member2){
        return member1 + member2;
    }
}

public class B extends A{
    /**
     * 减法
     * @param member1
     * @param member2
     * @return
     */
    @Override
    public int fun(int member1,int member2){
        return member1 - member2;
    }
}

public class TestAB {

    public static void main(String[] args) {
        A a = new A();
        A b = new B();
        System.out.println(a.fun(1,2));
        System.out.println(b.fun(1,2));
    }
}

//分析,由于子类重写了父类已经实现的方法,所以调用这两个会出现不一样的结果,这无疑会使系统更加地复杂。

改进后:
在这里插入图片描述

public abstract class Base {
    public abstract int fun(int member1,int member2);
}

public class A extends Base{
    /**
     * 加法
     * @param member1
     * @param member2
     * @return
     */
    @Override
    public int fun(int member1,int member2){
        return member1 + member2;
    }
}

public class B extends Base {
    /**
     * 减法
     * @param member1
     * @param member2
     * @return
     */
    @Override
    public int fun(int member1,int member2){
        return member1 - member2;
    }
}

public class TestAB {

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        System.out.println(a.fun(1,2));
        System.out.println(b.fun(1,2));
    }
}

//分析:使用了更抽象的类base,然后子类各自实现其抽象方法,使得程序更加地清晰。

在使用继承时,尽量遵循里氏替换原则,不要重写已实现父类的方法。

里氏替换原则告诉我们,继承实际上让两个类的耦合性增强了,在适当情况下可以通过聚合、组合、依赖来解决问题。

通常做法:原来的父类和子类继承同一个更抽象的基类,原有的继承去掉,采用依赖、聚合、组合。等关系代替。

开闭原则:

开闭原则:

  1. 是编程中最基础,最重要的设计原则。是其他原则的基础。其他原则都能看到这个原则的身影。
  2. 一个软件实体,如类,模块和函数应该对扩展开放(提供方),对修改关闭(使用方),用抽象构建框架,用实现扩展细节。
  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化(比如新增类),而不是修改已有的代码来实现。
  4. 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。

代码跟依赖倒置原则类似。

迪米特原则:

  1. 一个对象应该和其他对象保持最小的了解。
  2. 类与类之间的关系越密切,耦合度越大。
  3. 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道地越少越好。也就是说对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外除了提供public方法之外,不对外泄露任何信息。
  4. 迪米特法则还有个更简单的定义:只与直接朋友通信。

直接朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式有很多:依赖、关联、组合、聚合等。其中:我们称出现在成员变量、方法参数、方法返回值的类为直接的朋友,而出现在局部变量的类不是直接朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

就是别的类的逻辑信息不要出现在令一个类。而是完美的封装在原来的类里面,只暴露public方法供调用。

迪米特法则的注意事项和细节:

  1. 迪米特法则的核心是降低类之间的耦合。
  2. 由于每个类都减少了不必要的依赖,所以迪米特法则只是要求降低类(对象间的耦合关系),并不是完全消除耦合。

合成复用原则:

原则是尽量使用合成/聚合/组合的方式代替继承。

设计原则的核心思想:

  1. 找出应用中可能需要变化之处,把它们独立起来,不要和那些不需要变化的代码混在一起。
  2. 针对接口编程而不是针对实现编程。
  3. 为交互对象之间的松耦合而努力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值