设计模式之设计原则

一.设计原则:代表一组指南,帮助我们避免设计不良,它更多的是一种高度的抽象,思想。而设计模式可以看做设计原则的一种实现。

常见面向对象原则:


单一职责原则

    1.1 single responsibility principle:单一职责原则,一个类应该仅有一个引起它变化的原因,如果我们有2个以上要修改这个类的原因,那我们就要拆分它。

    1.2 为什么要这么做?

    想象一下,如果设计具有多个责任/实现多个功能的类。在开发时间内,你的类可以在自身中创建的依赖的数量是恐怖的。所以当你被要求改变某个功能时,你不能真正地确定它将如何影响类中实现的其他功能。你的更改可能会影响或可能不会影响其他功能,但您并不能承担风险,尤其是在生产应用程序中。所以你最终测试所有的依赖特性,那将耗费大量时间同时也不好维护。

     假如:要求您实现UserSettingService,其中用户可以更改设置,但在此之前,用户必须通过身份验证。

//bad example
public class UserSettingService
{
  public void changeEmail(User user)
  {
    if(checkAccess(user))
    {
       //Grant option to change
    }
  }
  public boolean checkAccess(User user)
  {
    //Verify if the user is valid.
  }
}
   

    看起来似乎不错,直到你想在其他地方重复使用checkAccess代码,或者你想改变checkAccess的方式,或者你想改变电子邮件的更方式。在后面的2个情况下,你最终都会改变这个类,所以有2个地方会引起这个类的变化。在第一种情况下,你必须使用UserSettingService来检查访问,这是不必要的。

    一种方法是将UserSettingService分解为UserSettingService和SecurityService。并将checkAccess代码移动到SecurityService。

//good example
public class UserSettingService
{
  public void changeEmail(User user)
  {
    if(SecurityService.checkAccess(user))
    {
       //Grant option to change
    }
  }
}
    

public class SecurityService
{
  public static boolean checkAccess(User user)
  {
    //check the access.
  }
}

    1.3 结论:单一职责原则代表着在应用程序的设计阶段标志类的好方法,它提醒你覆盖一个类可以发展的所有方式。 只有当应用程序应该如何工作的全部情况都被理解时,才能完成良好的责任分离。

开-闭原则    

    2.1 open-closed principle:对扩展开放,对修改关闭。合理抽象,分离出变化和不变化的部分,为变化的部分留下可扩展的功能。

    以下是违反开放关闭原则的示例。它实现一个图形编辑器处理不同形状的绘图。很明显,它不遵循开放关闭原则,因为GraphicEditor类必须为每个添加的新形状类进行修改。它有几个缺点:

a. 对于每个新的形状添加的GraphicEditor的单元测试应该重做。
b. 当添加新类型的形状时,添加它的时间将会很长,因为添加它的开发人员应该理解GraphicEditor的逻辑。
c. 添加新形状可能以不期望的方式影响现有功能,即使新形状完美地工作。

// Open-Close Principle - Bad example
 class GraphicEditor {
 
 	public void drawShape(Shape s) {
 		if (s.m_type==1)
 			drawRectangle(s);
 		else if (s.m_type==2)
 			drawCircle(s);
 	}
 	public void drawCircle(Circle r) {....}
 	public void drawRectangle(Rectangle r) {....}
 }
 
 class Shape {
 	int m_type;
 }
 
 class Rectangle extends Shape {
 	Rectangle() {
 		super.m_type=1;
 	}
 }
 
 class Circle extends Shape {
 	Circle() {
 		super.m_type=2;
 	}
 } 


    以下是支持开放关闭原则的示例。 在新设计中,我们在GraphicEditor中使用抽象draw()方法绘制对象,同时在具体形状对象中实现。 使用开放关闭原则避免了先前设计中的问题,因为添加新的形状类时,GraphicEditor不会更改,有以下优点:
  a. 无需单元测试。
b. 无需了解GraphicEditor的源代码。
c. 因为绘图代码被移动到具体的形状类,当添加新的功能时,降低了影响旧功能的风险。

// Open-Close Principle - Good example
 class GraphicEditor {
 	public void drawShape(Shape s) {
 		s.draw();
 	}
 }
 
 class Shape {
 	abstract void draw();
 }
 
 class Rectangle extends Shape  {
 	public void draw() {
 		// draw the rectangle
 	}
 } 

    2.2 结论:做出灵活的设计需要花费额外的时间和精力,并且它引入了新的级别的抽象,增加了代码的复杂性。 因此,这个原则应该应用于最有可能改变的领域。

里氏替换原则

    3.1 里氏替换原则LSP:如果程序模块使用的基类,则对基类的引用可以替换为派生类,而不会影响程序模块的功能,换句话说:子类必须能够替换它的父类。并且我们必须确保子类只是扩展而不替换父类的功能。 否则,新类在现有程序模块中使用时会产生不良影响。

    注意:这个原则与开放封闭原则(OCP)非常密切相关,如果违反LSP那么也将违反OCP。如果子类不能替换为父类引用,那么为了支持子类,我们继续对现有代码进行更改并添加支持,这将严重违反OCP。

    以下是违反里氏替换原则的典型示例。 在示例中使用2个类:Rectangle和Square。 让我们假设Rectangle对象在应用程序中的某处使用。 我们扩展应用程序并添加Square类。 正方形类由工厂模式返回,我们不知道确切的返回对象的类型。 但我们知道这是一个矩形。 我们得到矩形对象,将宽度设置为5,高度设置为10,并获取区域。 对于宽度为5和高度为10的矩形,区域应为50.然而,结果确是100。

// Violation of Likov's Substitution Principle
class Rectangle {
	protected int m_width;
	protected int m_height;

	public void setWidth(int width){
		m_width = width;
	}

	public void setHeight(int height){
		m_height = height;
	}


	public int getWidth(){
		return m_width;
	}

	public int getHeight(){
		return m_height;
	}

	public int getArea(){
		return m_width * m_height;
	}	
}

class Square extends Rectangle 
{
	public void setWidth(int width){
		m_width = width;
		m_height = width;
	}

	public void setHeight(int height){
		m_width = height;
		m_height = height;
	}

}

class LspTest {
	private static Rectangle getNewRectangle() {
		// it can be an object returned by some factory ... 
		return new Square();
	}

	public static void main (String args[]) {
		Rectangle r = LspTest.getNewRectangle();
        
		r.setWidth(5);
		r.setHeight(10);
		// user knows that r it's a rectangle. 
		// It assumes that he's able to set the width and height as for the base class

		System.out.println(r.getArea());
		// now he's surprised to see that the area is 100 instead of 50.
	}
}


    3.2 结论:在进行设计的时候,我们尽量从抽象类继承,而不是从具体类继承。如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口。

依赖倒置原则

    4.1 依赖倒置原则DIP:依赖抽象,不要依赖具体类。要求:高层模块不应该依赖于底层模块,两者都应该依赖于抽象;抽象不应该依赖于具体实现,而是具体实现依赖于抽象。

    下面是违反依赖反转原则的示例。我们有Manager,这是一个高级类,而低级类称为Worker。我们需要在我们的应用程序中添加一个新模块,以模拟由新的专业工作者雇用决定的公司结构的变化。我们为此创建了一个新类SuperWorker。
    让我们假设Manager类相当复杂,包含非常复杂的逻辑。现在我们必须改变它,以引入新的SuperWorker。让我们看看缺点:
a. 我们必须更改Manager类(记住它是一个复杂的,这将涉及时间和精力进行更改)。
b. Manager类中的某些当前功能可能会受到影响。
c. 单元测试应重做。
    所有这些问题可能需要很多时间来解决,它们可能在旧的功能中引入新的错误。如果应用程序是按照依赖反转原则设计的,情况会有所不同。这意味着我们设计了manager类,一个IWorker接口和实现IWorker接口的Worker类。当我们需要添加SuperWorker类时,我们所要做的就是为它实现IWorker接口。在现有类中没有其他更改。

// Dependency Inversion Principle - Bad example

class Worker {

	public void work() {

		// ....working

	}

}



class Manager {

	Worker worker;



	public void setWorker(Worker w) {
		worker = w;
	}

	public void manage() {
		worker.work();
	}
}

class SuperWorker {
	public void work() {
		//.... working much more
	}
}
    

    下面是支持依赖反转原理的代码。 在这个新设计中,通过IWorker接口添加了一个新的抽象层。 现在上面的代码中的问题被解决了(考虑到高级逻辑没有变化):
a. 在添加SuperWorkers时,Manager类不需要更改。
b. 最小化风险以影响Manager类中的旧功能,因为我们不更改它。
c. 无需为Manager类重做单元测试。

// Dependency Inversion Principle - Good example
interface IWorker {
	public void work();
}

class Worker implements IWorker{
	public void work() {
		// ....working
	}
}

class SuperWorker  implements IWorker{
	public void work() {
		//.... working much more
	}
}

class Manager {
	IWorker worker;

	public void setWorker(IWorker w) {
		worker = w;
	}

	public void manage() {
		worker.work();
	}
}


      4.2 结论:当应用这个原则时,它意味着高级类不直接与低级类工作,他们使用接口作为抽象层。 在这种情况下,不能使用运算符new来实现高级类中的低级对象。 相反,可以使用一些设计模式,例如工厂模式。


接口隔离原则

    5.1 接口隔离原则ISP:不应该强迫客户依赖于他们不用的方法。分离的方式:代理或者委托。

    下面是违反接口隔离原则的示例。我们有一个Manager类,代表管理工人的人。我们有两种类型的工人一些平常和一些非常有效的工人。这两种类型的工人工作,他们需要每天休息。但现在有些机器人来到他们工作的公司,他们不需要吃东西,所以他们不需要休息。一个新的机器人类需要实现IWorker界面,因为机器人工作。在另一方面,不必完全继承它,因为他们不吃东西。
   这就是为什么在这种情况下,IWorker被认为是一个污染的接口。
   如果我们保持当前的设计,新的Robot类被强制实现eat方法。我们可以编写一个不做任何事情的虚拟类(假设每天打断1秒),并且在应用中可能有不良影响(例如,管理者看到的报告会报告比所需人数多的午餐)。
    根据接口隔离原则,灵活的设计不会有接口污染。在我们的例子中,IWorker接口应该分为两个不同的接口。

// interface segregation principle - bad example
interface IWorker {
	public void work();
	public void eat();
}

class Worker implements IWorker{
	public void work() {
		// ....working
	}
	public void eat() {
		// ...... eating in launch break
	}
}

class SuperWorker implements IWorker{
	public void work() {
		//.... working much more
	}

	public void eat() {
		//.... eating in launch break
	}
}

class Manager {
	IWorker worker;

	public void setWorker(IWorker w) {
		worker=w;
	}

	public void manage() {
		worker.work();
	}
}
    

    下面是支持接口隔离原则的代码。 通过在2个不同的接口中拆分IWorker接口,新的Robot类不再强制实现eat方法。 此外,如果我们需要另一个功能来给机器人充电,我们创建另一个接口IRechargeble的方法充电。

// interface segregation principle - good example
interface IWorker extends Feedable, Workable {
}

interface IWorkable {
	public void work();
}

interface IFeedable{
	public void eat();
}

class Worker implements IWorkable, IFeedable{
	public void work() {
		// ....working
	}

	public void eat() {
		//.... eating in launch break
	}
}

class Robot implements IWorkable{
	public void work() {
		// ....working
	}
}

class SuperWorker implements IWorkable, IFeedable{
	public void work() {
		//.... working much more
	}

	public void eat() {
		//.... eating in launch break
	}
}

class Manager {
	Workable worker;

	public void setWorker(Workable w) {
		worker=w;
	}

	public void manage() {
		worker.work();
	}
}

最少知识原则(迪米特法则)

    6.1 最少知识原则LKP(迪米特法则):只与你的朋友谈话,减少对象之间的交互。

     哪些是朋友:当前对象本身;通过方法的参数传递进来的对象;当前对象创建的对象;当前对象的实例变量引用的对象;方法内所创建或引用的对象。

public class LawOfDemeterInJava
{
  private Topping cheeseTopping;
  
  /**
   * Good examples of following the Law of Demeter.
   */
  public void goodExamples(Pizza pizza)
  {
    Foo foo = new Foo();
    
    // (1) 可以调用我们自己的方法
    doSomething();
    
    // (2) 可以调传递进来参数的方法
    int price = pizza.getPrice();
    
    // (3) 可以调我们自己创建对象的方法
    cheeseTopping = new CheeseTopping();
    float weight = cheeseTopping.getWeightUsed();
    
    // (4) 任何直接持有的组件对象
    foo.doBar();
  }
  
  private void doSomething()
  {
    // do something here ...
  }
}


//In short, the Law of Demeter aims to keep you from doing things like this:

objectA.getObjectB().doSomething();
//or even worse, this:

objectA.getObjectB().getObjectC().doSomething();

    6.2 结论:使用迪米特法则:你的类将“松耦合”; 你的依赖性降低;重用你的类将更容易;你的类在其他类中不太需要修改;你的代码将更容易测试。

    7.其他原则:面向接口编程;优先使用组合而非继承。


二.设计模式:是指在软件开发过程中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。

            组成:模式名称,环境和问题,解决方案,效果。

            分类:

  • a. 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
  • b. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  • c. 行为型模式:模版方法模式、命令模式、迭代器模式观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。

接口实现“封装隔离”。接口和抽象类使用原则:优先使用接口;在既要定义子类的行为,又要为子类提供公共的功能的时候用抽象类。



参考文献:研磨设计模式

参考案例:点击打开链接


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值