设计模式------常见面向对象的原则

重新认识面向对象

封装变化

从宏观层面来看,面向对象的构建方式更能适 应软件的变化,能将变化所带来的影响减为最小。 封装变化包含两层含义:

  • 将相同的变化封装到一个接口或抽象类中

  • 将不同的变化封装到不同的接口或抽象类中,不应该有 两个不同的变化出现在同一个接口或抽象类中。

封装变化,就是受保护的变化,找出预计有变化或不稳定 的点,为这些变化点创建稳定的接口。

抽象约束

抽象是对一组事物的通用描述,没有具体的 实现,就表示它可以有非常多的可能性,可以随需求的变 化而变化。 通过接口或抽象类可以约束一组可能变化的行为,并且能 够实现对扩展开放,其包含三层含义:

  • 通过接口或抽象类约束扩展,对扩展进行边界限定, 不允许出现在接口或抽象类中不存在的public方法。
  • 参数类型,引用对象尽量使用接口或抽象类,而不是 实现类,这主要是实现里氏替换原则的一个要求。
  • 抽象层尽量保持稳定,一旦确定就不要修改。

各司其职

  • 从微观层面来看,面向对象的方式更强调各个类的“ 责任”
  • 由于需求变化导致的新增类型不应该影响原来类型的 实现——各负其责。

软件设计的要求

  1. 使程序呈现高内聚,低耦合的特性
  2. 可维护性:代码完成之后,当需要修改程序的某个模块 时,对其他模块的影响和修改的代价。——需要修改的地 方很少,就是容易维护。
  3. 可扩展性:代码完成之后,当需要为程序添加新的功能 时,对其他模块的影响和添加的代价。——只需要添加添 加该功能的代码,不需要修改原来的代码(对之前的代码 没有影响),这就是可扩展。
  4. 重用性:代码完成之后,以后开发中可以复用部分代码, 提高效率,就是复用性强。
  5. 灵活性:代码完成之后,使用的地方可以通过多种方式 来调用该部分的代码,这就是灵活性好。
  6. 可读性:编程规范性,便于其他程序员的阅读和理解 (命名规范、代码排版、关键注释)。
  7. 可靠性:软件在一定的边缘条件下的出错机率、性能劣 化趋势等,又称稳定性。要求系统在发生硬件故障,软件 故障,或人为错误时,仍然可以正常工作。
  8. 可移植性:代码完成之后,稍微修改一下就可以在另外 一个环境中使用, 也就是说可以在两个环境以上使用, 就 具备可移植性。

设计模式七大原则

  1. 单一职责原则(SRP)
  2. 开放-封闭原则(OCP)——面向对象设计的终极目标
  3. 里氏代换原则(LSP)
  4. 依赖倒转原则(DIP)
  5. 合成/聚合复用原则(CARP)
  6. 接口隔离原则(ISP)
  7. 迪米特法则(LoD)

单一职责原则

职责就是对象能够承担的责任,并以某种行为方式来执行 。可以将数据库的增删改查理解成职责,即对数据的基本 操作。单一职责原则对类来说,即一个类应该只负责一项职责。

交通工具类
//方式1 
class Vehicle{
 public void run(String vehicle) { System.out. println(vehicle + " 在公路上跑 ");}
  } 
 public class singleResponsibility { 
 public static void main(String[] args) {
  Vehicle vehicle = new Vehicle(); 
  vehicle.run("摩托车"); 
  vehicle.run("汽车"); 
  vehicle.run("飞机");
  } 
  } 

方案1的分析
1.在方式1的run方法中,违反 了单一职责原则
2.解决方案:根据交通工具运行 方法不同,分解成不同类

//方式2:根据交通工具运行方法不同,分解成不同类 
class RoadVehicle { 
public void run(String vehicle) { 
System .out. println(vehicle + " 在公路上跑 "); 
 	}
  }
class AirVehicle {
 public void run(String vehicle) { 
 System.out. println(vehicle + " 在天空中飞 ");
   } 
}
class WaterVehicle {
 public void run(String vehicle) { 
 System.out. println(vehicle + " 在水里游 "); 
  } 
} 
public class singleResponsibility { 
public static void main(String[] args) { 
RoadVehicle roadVehicle = new RoadVehicle();
 roadVehicle.run("摩托车"); 
 roadVehicle.run("汽车"); 
 AirVehicle airVehicle = new AirVehicle(); 
 airVehicle.run("飞机"); 
 WaterVehicle waterVehicle = new WaterVehicle(); 
 waterVehicle.run("轮船"); 
 }
}
 

方案2的分析
1.遵守单一职责原则
2.但是改动很大,即要将类分解,又要修改客户端
3.改进:保留并修改Vehicle类,改动的代码会比较少

//方案3 保留并修改Vehicle类 
class Vehicle2 { 
public void run(String vehicle) {  //不变 
System .out. println(vehicle +"在公路上跑"); } 
public void runAir(String vehicle) { //增加 
System.out. println(vehicle + "在天空中飞"); }
 public void runWater(String vehicle) { //增加 
 System.out. println(vehicle + "在水里游"); } }
public class singleResponsibility { 
public static void main(String[] args) { 
Vehicle2 Vehicle = new Vehicle2(); 
Vehicle.run("摩托车"); 
Vehicle.run("汽车");
 Vehicle.runAir("飞机");
  Vehicle.runWater("轮船"); 
  } 
  }

方案3的分析
1.这种修改方法没有对原来的类做大的修改,只是增加方法;
2.增加的部分不影响原有部分,降低了变更引起的风险;
3.这里在类这个级别上没有遵守单一职责原则,但是在方法级 别上,仍然是遵守单一职责。
单一职责原则注意事项和细节
1)降低类的复杂度,一个类只负责一项职责。
2)提高类的可读性,可维护性
3)降低变更引起的风险
4)通常情况下,
应当遵守单一职责原则。
只有逻辑足够简单,才可以在代码级违反单一职责原则:
只有类中方法数量足够少,可以在方法级别保持单一职责 原则。

开放—封闭原则

基本介绍
开闭原则(Open Closed Principle)是编程中最基础、 最重要的设计原则——面向对象设计的终极目标 。
一个软件应当对扩展开放,对修改关闭。
开闭原则(OCP)是面向对象设计中“可复用设计”的 基石,是面向对象设计中最重要的原则之一,其它很多 的设计原则都是实现开闭原则的一种手段。
编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
怎么做?
核心--------抽象

画图类
//方式1
class Rectangle{ 
	void draw(){ System.out.println("矩形"); } 
	} 
class Circle { void draw(){ System.out.println("圆形"); } 
	} 
class Triangle {  //新增三角形——肯定要改 
	void draw(){ System.out.println("三角形"); }
	 }
class GraphicDraw { 
	void drawgraph(int type){ 
	if(type==1){ 
		Rectangle rec=new Rectangle(); 
		rec.draw();
		} 
	else if(type==2){ 
		Circle c=new Circle(); 
		c.draw();
		}
	 else if(type==3){  //新增绘制三角形 
	 Triangle t=new Triangle(); 
	 t.draw();} 
	 } 
	 } 
public class graphDraw { 
	public static void main(String[] args) { 
		GraphicDraw graphicdraw=new GraphicDraw(); 
		graphicdraw.drawgraph(2); //客户端肯定要改
}
}

方式1的优缺点:

  1. 优点是比较好理解,简单易操作。
  2. 缺点是违反了开闭原则(对扩展(提供方)开放,对 修改(使用方)关闭):需要给类增加新功能的时候,尽量不要修改代码,或者尽可能少修改代码。
  3. 本例,需要新增一个图形种类时,修改的地方比较多。
//方式2
//Shape类,基类 
abstract class Shape { 
	public abstract void draw(); //抽象方法 
	} 
class GraphicDraw {  //新增绘制图形不需修改此类 
	void drawgraph(Shape s){ s.draw(); 
	} 
}
class Rectangle extends Shape { 
	void draw(){ System.out.println("矩形"); 
	} 
} 
class Circle extends Shape { 
	void draw(){ System.out.println("圆形"); 
	} 
} 
class Triangle extends Shape {  //新增三角形 
	void draw(){ System.out.println("三角形"); 
	} 
}
public class graphDraw { 
	public static void main(String[] args) { 
			GraphicDraw graphicdraw=new GraphicDraw(); 
			graphicdraw.drawgraph(new Circle()); 
			graphicdraw.drawgraph(new Rectangle()); 
			graphicdraw.drawgraph(new Triangle()); 
		} 
	}

改进思路: 发现Rectangle和Circle有共同的draw方法,创 建抽象Shape类做成基类,并提供抽象的draw方法,让子类 去实现,当需要新的图形时,只需要让新的图形继承Shape, 并实现draw方法。画图类GraphicDraw不出现具体的类, 用抽象类Shape。这样使用方的代码就不需要修改,满足开 闭原则。

里氏代换原则

继承的优点:

  1. 子类拥有父类的所有方法和属性,从而可以减少创建类的工 作量。
  2. 提高了代码的重用性。
  3. 提高了代码的扩展性,子类不但拥有了父类的所有功能,还 可以添加自己的功能。

继承的缺点:

  1. 继承是侵入性的。只要继承,就必须拥有父类的所有属性和 方法。
  2. 降低了代码的灵活性。因为继承时,父类会对子类有一种约 束。
  3. 增强了耦合性。当需要对父类的代码进行修改时,必须考虑 到对子类产生的影响。有时修改了一点点代码都有可能需要 对大段程序进行重构。

继承包含这样一层含义:父类中已经实现的方法,实际上是 在设定规范和契约,虽然它不强制要求所有的子类必须遵 循这些契约,但是如果子类对这些已经实现的方法任意修 改,就会对整个继承体系造成破坏。

如何正确使用里氏代换原则

  1. 子类型(subtype)必须能够替换它们的基(父)类型。(子 类可以以父类的身份出现)
  2. 是关于继承机制的原则,是实现开放-封闭原则的具体规 范,违反了里氏代换原则必然违反了开放-封闭原则。
  3. 只要有父类出现的地方,都可以使用子类来替代。而且不会出现任何错误或者异常。
  4. 但是反过来却不行,子类出现的地方,不能使用父类来替代。
  5. 里氏代换原则的主要作用是规范继承时子类的一些书写规则。其主要目的就是保持父类方法不被覆盖。
  6. 在适当的情况下,可以不使用继承而是通过聚合,组合, 依赖来解决问题。

有如下程序

class A{
	public int func1(int num1, int num2){
		return num1-num2;  }
}
class B extends A{
  public int func1(int a,int b){
    return a+b;}
  public int func2(int a, int b){
    return func1(a,b)+9;}
}
public class Liskov {

	public static void main(String[] args) {
		A a = new A();
		System.out.println("11-3=" +a.func1(11,3));
		System.out.println("1-8="+a.func1(1,8));
		System.out.println ("-------");
		B b = new B();
		System.out.println("11-3=" + b.func1(11,3));
		//这里本意是求出11-3
		System.out.println("1-8="+ b.func1(1,8));
		System.out.println("11+3+9="+b.func2(11,3));
	}
}

问题所在
是类B重写了父 类的方法func1,造成原有功能出现错误。

如何解决
通用的解决思路:
让原来的父类和子类都继承一个更通俗的基类, 原有的继承关系去掉, 采用依赖,聚合,组合等关系代替。

//创建-个更加基础的基类
class Base {
//把更加基础的方法和成员写到Base类
}
// A类
class A extends Base {
//返回两个数的差
  public int func1(int num1, int num2) {
	  return num1 - num2;  }
}
class B extends Base{
  A a=new A();
  public int func1(int a,int b){
	  return a+b;	}
  public int func2(int a, int b){
	  return func1(a,b)+9;	}
  public int func3(int a, int b){
	  return this.a.func1(a,b);	}
}

public class Liskov {
  public static void main(String[] args) {
	A a = new A();
	System.out.println("11-3=" +a.func1(11,3));
	System.out.println("1-8="+a.func1(1,8));
	System.out.println ("-------");
	B b= new B();
	//因为B类不再继承A类,因此调用者,不会再func1是求减法
	//调用完成的功能就会很明确
	System. out.println("11+3=" + b. func1(11, 3));//这里本意是求出11+3
	System. out.println("1+8=" + b.func1(1, 8));// 1+8
	System. out.println("11+3+9=" + b.func2(11, 3));
	//使用组合仍然可以使用到A类相关方法
	System. out.println("11-3=" + b.func3(11, 3));// 这里本意是求出11-3
	}	
}

里氏代换原则规定,子类不能覆写父类已实现的方法。父 类中已实现的方法其实是一种已定好的规范和契约,如 果随意修改它,那么可能会带来意想不到的错误。

依赖倒转原则

基本介绍

依赖倒转原则(Dependence Inversion Principle)是指:

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

精简定义:面向接口编程(Object-Oriented Design, OOD)
好处: 采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

实现person类

public class DependecyInversion { 
	public static void main(String[] args) {
		 Person person = new Person(); 
		 person.receive(new Email());    
		 }
	 }
 class Email{ public String getInfo() { 
 		return "电子邮件信息: hello world ";    
 	} 
 }
  /*  Person 类 接受消息,将Email类 作为参数 产生了依赖 *  
  如果 参数发生变化,即接受的是微信 或短信 整个方法需要改动 */ 
  class Person{ public void receive(Email email) { 
  		System.out.println(email.getInfo());    
  	} 
  } 

改进

/*定义接受的接口 */ 
interface IReciver{ 
	public String getInfo();        
} 
class Email implements IReciver{ public String getInfo() { 
		return "电子邮件信息: hello world ";    
	} 
} 
class WenXin implements IReciver{ 
	public String getInfo() { 
		return "微信信息: hello WenXin ";   
	 } 
 }

注意事项和细节

  1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳 定性更好。
  2. 变量的声明类型尽量是抽象类或接口,这样变量引用和实际对 象间,就存在一个缓冲层,利于程序扩展和优化。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值