JAVA基础--面向对象设计原则

      面对对象开发过程中,有七个基本原则(开闭原则、单一职责原则、里式替换原则、依赖倒置原则、合成聚合原则、接口隔离原则、迪米特原则)。

1、开闭原则

      1)定义:软件方法应该对修改关闭,对扩展开放。

      2)问题由来:随着软件的运行,因为功能的扩展、业务的调整、升级等原因,修改原有代码,可能会给旧代码引入新的问题,产生新的bug,也可能需要重构原有代码,整体功能都需要重新测试,产生额外的工作量。

      3)解决方案:当软件需要调整时,尽量使用功能扩展的方式,增加新的方法来实现,避免修改原有代码。

      实现方式:①抽象是关键,软件中如果没有抽象类或者接口,软件就没有了扩展点。②封装可变性,将软件中的可变因素封装到一个集成结构中,如果多个可变因素混杂,会导致软件系统复杂而混乱。

      开闭原则是对象设计中的最基本的设计原则,其他的设计原则及23种设计模式都遵循开闭原则。

2、单一职责原则

      1)定义:类中不要存在多余一个导致累变更的原因,一个类只做它自己做的事(高内聚)。

      2)问题由来:一个类同时包含了A职责以及B职责,当A职责需要改变时,可能会引起不相关的B职责的功能,产生额外的不可预期的问题。

      3)解决方案:遵循单一职责原则,设计两个类,分别实现A和B职责,使A职责与B职责分离,这样当修改其中一个职责时,不会影响到别的职责功能。

      虽然单一职责原则很简单,不少人会不屑一顾,即使没有了解过设计原则,在开发中也会应用到,这是常识。但是实际开发中,即使是自身开发工程师,也有可能会违背该原则,这是为什么呢?这是因为功能的扩展,可能在最初设计的时候是符合单一职责原则,但是随着业务的发展,原有的业务需要调整,这个时候需要增加新的职责,如果遵循单一职责原则,可能改动会比较大。

       比如职责P是符合单一职责原则的,这个时候要增加新的职责,遵循单一职责原则就需要将原有的类拆分成P1和P2两个类,并且调用的方法也需要修改,改动较大。如果直接在P职责的类中增加新的职责,只需要改动P职责一个地方,改动较小。但是这么做又有风险,因为我们无法预估以后会不会继续扩展出P3、P4。。。

        4)单一职责原则的优点:

              ①可以降低累的复杂度,一个类只有一个职责,逻辑更加清晰;

              ②提高代码可读性,提高系统的可维护性;

              ③降低变更带来的风险。

3、里氏替换原则

        1)定义:所有父类出现的地方都可以用子类替换,并且使用子类替换不会出错或异常。

        2)问题由来:业务逻辑原本由父类F完成,功能扩展后,遵循单一职责原则,新建继承F父类的子类S来完成新功能,如果重写的方法,在引用S子类后,可能会导致原有的F父类功能异常。

        3)解决方案:遵循里氏替换原则,保持原有的父类方法,除了扩展新的功能,不修改原有的父类方法。

             ①子类必须完全实现父类的方法。

                 如果子类不能完全实现父类的方法,建议断开父子关系,采用Java类之间关联关系的另外三种依赖、关联、组合去实现。

             ②子类可以增加自己的特有方法。

                 子类可以实现自己的新功能,子类可以出现在父类出现的地方,相反,父类却不可以出现在子类出现的地方。

            ③子类重载父类方法时,参数范围要比父类的范围更大。

            ④子类重写或实现父类方法时,返回值范围要比父类更小。

4、依赖倒置原则

        1)定义:高层的类不应该以来低层的类,二者都依赖于抽象。抽象不依赖于细节,细节依赖于抽象。

        2)问题由来:类A依赖于类B,当业务扩展时,A需要依赖于类C,这个时候直接修改A,就可能会因为新的问题,产生不必要的缺陷。

        3)解决方案:采用依赖倒置原则,将B与C依赖于同样的接口,A通过接口与B和C产生关系,降低直接修改A产生的风险。

        4)依赖倒置就是面向接口编程,示例:

class XiaoMing{
    //在这里产生了实体类之间的依赖
    public void drink(Milk milk){
        milk.run();
    }
            
}
class Milk{
    
    public void run(){
        System.out.println("drink milk......");
    }
    
}

public class Client{
    public static void main(String[] args){
        XiaoMing xiaoming= new XiaoMing();
        people.drink(new Milk());
    }
}   

        当小明不想喝牛奶,想喝橙汁,除了需要增加橙汁的实现,还需要修改提供给小明喝的饮料入口,以及小明具体喝的饮料

class XiaoMing{
    //在这里产生了实体类之间的依赖
    public void drink(Juice juice){
        juice.run();
    }
            
}
class Milk{
    
    public void run(){
        System.out.println("drink milk......");
    }
    
}

class Juice{
    
    public void run(){
        System.out.println("drink milk......");
    }
    
}

public class Client{
    public static void main(String[] args){
        XiaoMing xiaoming= new XiaoMing();
        people.drink(new Juice());
    }
}   

         小明只是换一个饮料,却需要改动如此多的代码,风险很大,正确做法应该是给饮品增加一个IDrink接口。

interface IDrink{
    public void run();
}

class XiaoMing{
    //在这里产生了实体类之间的依赖
    public void drink(IDrink idrink){
        idrink.run();
    }
            
}
class Milk implements IDrink {
    
    public void run(){
        System.out.println("drink milk......");
    }
    
}

class Juice implements IDrink {
    
    public void run(){
        System.out.println("drink milk......");
    }
    
}

public class Client{
    public static void main(String[] args){
        XiaoMing xiaoming= new XiaoMing();
        people.drink(new Milk());
        people.drink(new Juice());
    }
}   

        这样修改,哪怕小明后期想喝水,只需要增加Water类实现IDrink接口,喝的时候直接给Water即可。

5、接口隔离原则

        1)定义:一个类不应该实现它不需要的接口。一个类对另一个类的依赖建立在最小接口上。

        2)问题由来:A类通过接口I引用B类,C类通过接口I引用D类,A和C之间有不同的实现接口,则B和D不是最小接口,需要实现他们不需要的接口。

        3)解决方案:将接口I拆分非几个独立的接口,分别为A和C提供接口。

        4)代码示例:

interface I {
	public void method1();
	public void method2();
	public void method3();
	public void method4();
	public void method5();
}

class A{
	public void depend1(I i){
		i.method1();
	}
	public void depend2(I i){
		i.method2();
	}
	public void depend3(I i){
		i.method3();
	}
}

class B implements I{
	public void method1() {
		System.out.println("类B实现接口I的方法1");
	}
	public void method2() {
		System.out.println("类B实现接口I的方法2");
	}
	public void method3() {
		System.out.println("类B实现接口I的方法3");
	}
	//对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法,
	//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
	public void method4() {}
	public void method5() {}
}

class C{
	public void depend1(I i){
		i.method1();
	}
	public void depend2(I i){
		i.method4();
	}
	public void depend3(I i){
		i.method5();
	}
}

class D implements I{
	public void method1() {
		System.out.println("类D实现接口I的方法1");
	}
	//对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,
	//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
	public void method2() {}
	public void method3() {}

	public void method4() {
		System.out.println("类D实现接口I的方法4");
	}
	public void method5() {
		System.out.println("类D实现接口I的方法5");
	}
}

public class Client{
	public static void main(String[] args){
		A a = new A();
		a.depend1(new B());
		a.depend2(new B());
		a.depend3(new B());
		
		C c = new C();
		c.depend1(new D());
		c.depend2(new D());
		c.depend3(new D());
	}
} 

          接口I过于臃肿,B和D都需要实现不必要的方法,过于繁琐,可以如下修改:

 interface I1 {
	public void method1();
}

interface I2 {
	public void method2();
	public void method3();
}

interface I3 {
	public void method4();
	public void method5();
}

class A{
	public void depend1(I1 i){
		i.method1();
	}
	public void depend2(I2 i){
		i.method2();
	}
	public void depend3(I2 i){
		i.method3();
	}
}

class B implements I1, I2{
	public void method1() {
		System.out.println("类B实现接口I1的方法1");
	}
	public void method2() {
		System.out.println("类B实现接口I2的方法2");
	}
	public void method3() {
		System.out.println("类B实现接口I2的方法3");
	}
}

class C{
	public void depend1(I1 i){
		i.method1();
	}
	public void depend2(I3 i){
		i.method4();
	}
	public void depend3(I3 i){
		i.method5();
	}
}

class D implements I1, I3{
	public void method1() {
		System.out.println("类D实现接口I1的方法1");
	}
	public void method4() {
		System.out.println("类D实现接口I3的方法4");
	}
	public void method5() {
		System.out.println("类D实现接口I3的方法5");
	}
} 

       5)接口隔离原则与单一职责原则的比较:

        ①单一职责原则主要约束的是类,其次才是接口、抽象,接口隔离原则约束的是接口;

        ②单一职责原则强调的是职责,由业务逻辑划分;接口隔离强调的是接口依赖间的隔离。

6、合成聚合原则

       1)定义:经常又叫做合成复用原则(Composite ReusePrinciple或CRP),尽量使用对象组合,而不是继承来达到复用的目的。

       2)问题由来:继承复用破坏包装,因为继承将超类的实现细节暴露给了子类;合成聚合耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。

       3)解决方案:组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。

       4)示例

class A {
    private B b;

    public A (){
        b = new B();
    }
}


class B {
}

迪米特原则

        1)定义:一个对象应该对其他对象保持最少的了解。

        2)问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

        3)解决方案:尽量降低类与类之间的耦合。一个对象应该对其他对象有最少的了解,也就是说一个类要对自己需要耦合或者调用的类知道的最少。我只知道你有多少public方法可以供我调用,而其他的一切都与我无关。

        4)迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值