面向对象的六大原则

面向对象的六大原则
1.单一职责原则(Single Responsibility Principle) 单一职责原则缩写SRP,定义为:就一个类而言,应该仅有一个引起它变化的原因,简单来说,一个类中应该是一组相关性很高的函数、数据的封装,但单一职责的划分界限总是不那么清晰,很多时候都是需要靠个人经验来界定的,当然,最大的问题是对职责的定义,什么是类的职责,以及怎么划分类的职责。
如何划分一个类、一个函数的职责,每个人都有自己的看法,这需要根据个人经验、具体的业务逻辑而定,但是,它也有一些基本的指导原则,例如,两个完全不一样的功能就不应该放在一个类中。一个类中应该是一组相关性很高的函数、数据的封装。工程师可以不断地审视自己的代码,根据具体的业务、功能对类进行相应拆分,这是程序员优化代码的第一步。
2. 让程序更稳定、更灵活的开闭原则(Open Close Principle) 开闭原则缩写OCP,它是java世界里最基础的设计原则,它知道我们如何建立一个稳定、灵活的系统。开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改应是封闭的。在软件的生命周期中,因为变化、升级和维护等原因需要对原有代码进行修改时,可能会将错误引入原本已经经过测试的旧代码中,破坏原有系统。因此,当软件需要变化时我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有代码来实现。
当然在现实开发中,只通过继承的方式来升级、维护原有系统只是理想化的愿景,因此,在实际开发中,修改原有代码和扩展代码往往是同时存在的。
3. 构建扩展性更好的系统,里氏替换原则(Liskov Substitution Principle) 里氏替换原则缩写LSP,它的定义是:如果对每一个类型为S的对象O1,都有类型T的对象O2,使得T定义的所有程序P在所有的对象O1都替换为O2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
第二种定义是:所有引用基类的地方必须能透明地使用子类的对象,通俗的讲只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误和异常,而反过来就不行了。———多态

public class Window{ public void show(View child){ child.draw() } }public abstract class View{ public abstract void draw(); public void measure(int width,int height){ //测量视图的大小 } }public class TextView extends View{ public void draw(){ //绘制文本 } }public class ImageView extends View{ public void draw(){ //绘制图片 } }

Window依赖于View,而View定义了一个视图抽象,measure是各个子类共享的方法,子类通过覆写View的draw方法实现具有各种特色的功能,任何继承自View类的子类都可以传递给show函数,这就是所说的里氏替换原则,就可以定义各种各样的View然后传递给Window,Window负责组织View,将View显示到屏幕上。
4. 让项目拥有变化的能力,依赖倒置原则(Dependence Inversion Principle) 依赖倒置原则缩写DIP,依赖倒置原则指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次模块的实现细节的目的。依赖倒置原则有以下几个关键点:
  1. 高层模块不应该依赖底层模块,两者都应该依赖其抽象
  2. 抽象不应该依赖细节
  3. 细节应该依赖抽象
在java中抽象指的是抽象类或者接口,细节这的是实现接口或者继承抽象类而产生的类,其特点是可以实例化。高层模块就是调用端,底层模块就是具体实现类。
依赖倒置原则在java语言中的表现是:模块之间依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的,一句话概括就是面向接口编程或者说是面向抽象编程。
package DIPDemo; /** * * @author houyl *依赖倒置原则 */ interface IPerson { void work(); void eat(); }public class NormalPerson implements IPerson{@Override public void work() { // TODO Auto-generated method stub System.out.println("做普通的工作"); }@Override public void eat() { // TODO Auto-generated method stub System.out.println("吃快餐"); }}public class Student implements IPerson{@Override public void work() { // TODO Auto-generated method stub System.out.println("工作就是学习"); }@Override public void eat() { // TODO Auto-generated method stub System.out.println("吃食堂"); }}public class Teacher implements IPerson{@Override public void work() { // TODO Auto-generated method stub System.out.println("教书"); }@Override public void eat() { // TODO Auto-generated method stub System.out.println("吃教职工食堂"); }}public class TheirLife { IPerson person = new NormalPerson(); //只有一个默认实现 //Teacher t=new Teacher(); //依赖于细节 public void work() { person.work(); }public void eat() { person.eat(); } //依赖于抽象 public void setPerson(IPerson person) { this.person = person; }}public class Test { public static void main(String[] args) { IPerson p=new Teacher(); TheirLife tl=new TheirLife(); tl.setPerson(p); tl.work(); tl.eat(); } }

5. 系统有更高的灵活性,接口隔离原则(InterfaceSegregation Principle) 接口隔离原则缩写ISP,它的定义是,客户端不应该依赖于它不需要的接口,另一种定义是:类间的依赖关系应该建立在最小的接口上,接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法,接口隔离原则目的是系统解开耦合,从而容易重构、更改和重新部署。
例如:
public class CloseUtils { public static void closeQuitely(Closeable closeable) { if(null!=closeable) { try { closeable.close(); }catch (IOException e) { e.printStackTrace(); } } } }public class Test { public static void main(String[] args) {File f =new File("a.txt"); FileOutputStream fos=null; try { fos = new FileOutputStream(f); fos.write("你好".getBytes()); } catch (IOException e) { e.printStackTrace(); }finally { CloseUtils.closeQuitely(fos); }}

建立一个类专门关闭IO流对象,因为很多IO相关的对象都实现了这个Closeable接口。CloseUtils的closeQuietly方法的基本原理就是依赖于Closesable抽象而不是具体实现(依赖倒置原则),并且建立在最小化依赖的原则的基础上,它只需知道这个对象是可关闭的,其他的一概不关心,也就是接口隔离原则。
6. 更好的可扩展性,迪米特原则(Law of Demeter) 迪米特原则缩写LOD,也称为最少知识原则,虽然名字不同,但描述的是同一个原则,即:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或者调用的类知道的最少,类的内部如何实现与调用者或者依赖者没关系,调用者或者依赖者只需知道它需要的方法即可,其他的可一概不管。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类影响也越大。
看下面的代码:
package LODDemo; public class Room { public float area; public float price; public Room(float area, float price) { this.area = area; this.price = price; }@Override public String toString() { return "Room [area=" + area + ", price=" + price + "]"; }}//中介 public class Mediator { List mRooms = new ArrayList<>(); public Mediator() { for (int i = 0; i < 5; i++) { mRooms.add(new Room(14 + i, (14 + i) * 150)); } }public List getAllRooms() { return mRooms; } }//租户 public class Tenant { public void rentRoom(float roomArea, float roomPrice, Mediator mediator) { List rooms = mediator.getAllRooms(); for (Room room : rooms) { if (isSuitble(roomArea, roomPrice, room)) { System.out.println("租到房了" + room); break; } } }private boolean isSuitble(float roomArea, float roomPrice, Room room) { // TODO Auto-generated method stub return roomArea >= room.area && roomPrice <= roomPrice; } }

上面代码中租户不仅依赖了中介还频繁地与Room类打交道,租户只是通过中介找到一间适合自己的房子,如果把这些检测条件都放在租客上,就会弱化中介的功能,而且导致租客和Room的耦合度增加。
优化后:
public class Room { public float area; public float price; public Room(float area, float price) { this.area = area; this.price = price; }@Override public String toString() { return "Room [area=" + area + ", price=" + price + "]"; }}public class Mediator { List mRooms = new ArrayList<>(); public Mediator() { for (int i = 0; i < 5; i++) { mRooms.add(new Room(14 + i, (14 + i) * 150)); } }public Room rentOut(float area, float price) { for (Room room : mRooms) { if (isSuitble(area, price, room)) { return room; } } return null; }private boolean isSuitble(float roomArea, float roomPrice, Room room) { // TODO Auto-generated method stub return roomArea >= room.area && roomPrice <= roomPrice; } }public class Tenant { public void rentRoom(float roomArea, float roomPrice, Mediator mediator) { System.out.println("租到房了" + mediator.rentOut(roomArea, roomPrice)); }}

【面向对象的六大原则】这样,租客只和中介通信,不会牵扯到具体的房子

    推荐阅读