程序设计6原则
ref: <<设计模式之禅>>
开放封闭原则(Open Close Principle,简称OCP) - 尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
总纲领
单一职责原则(Single Responsibility Principle,简称SRP )- 针对类来说的;
所以一个类/接口的职责应该尽可能少
接口隔离原则(Interface Segregation Principle,简称ISP) - 针对接口来说的;
尽量细化接口,接口中的方法尽量少
里氏替换原则(Liskov Substitution Principle,简称LSP) - 在使用基类的的地方可以任意使用其子类,能保证子类完美替换基类。
尽量不要破坏继承体系
依赖倒置原则(Dependence Inversion Principle,简称DIP) - 高层模块不应该依赖底层模块, 而是底层模块注入高层模块
也就是 "面向接口编程"
迪米特法则(Law of Demeter,简称LoD) - 类间解耦(一个类对自己依赖的类知道的越少越好)。方法的权限修饰符尽可能小; 加入中间类
开-闭原则(总)
和依赖倒置原则有点类似, 但是有区别, 我的感觉是开闭原则更加偏向的是使用抽象来避免修改源代码,主张通过扩展去应对需求变更,而依赖倒置更加偏向的是层和层之间的解耦。
这儿有个例子: 假设需要一个发送节日祝福的功能, 通过短信
先看 bad demo:
/**
* 信息发送类, 通过短信发送
*
* @version: 0.1
* @author: xy
* @date: 2018年1月23日 下午10:27:28
*/
public class MessageSend {
public void send(String msg) {
System.out.println("Text Message send : " + msg);
}
}
/**
* 服务类, 供客户端调用
*
* @version: 0.1
* @author: xy
* @date: 2018年1月23日 下午10:29:09
*/
public class MessageService {
private MessageSend sender;
public MessageService() {
this.sender = new MessageSend();
}
public void send(String msg) {
sender.send(msg);
}
}
/**
* 客户端调用
*
* @version: 0.1
* @author: xy
* @date: 2018年1月23日 下午10:30:11
*/
public class MessageClient {
public static void main(String[] args) {
MessageService messageService = new MessageService();
messageService.send("Merry Christmas !");
}
}
/////////////////////////////////////////////////////
// 现在需要新支持另一种发送方式: 微信, 只好修改源码
/**
* 新增的信息发送帮助类, 支持email
*
* @version: 0.1
* @author: xy
* @date: 2018年1月23日 下午10:37:55
*/
public class EmailMessageSend {
public void send(String msg) {
System.out.println("Email Message send: " + msg);
}
}
/**
* 新增的信息发送类型
*
* @version: 0.1
* @author: xy
* @date: 2018年1月23日 下午10:42:36
*/
public enum SendType {
/**
* 短信
*/
TEXT,
/**
* 邮件
*/
EMAIL
}
/**
* 服务类, 供客户端调用
*
* @version 0.1
* @author xy
* @date 2018年1月23日 下午10:53:18
*/
public class MessageService {
private MessageSend sender;
private SendType t;
public MessageService(SendType t) {
this.t= t;
}
public void send(String msg) {
// 根据不同的类型进行不同的处理
}
}
// 然后是client增加新的调用
// 后续如果又要支持新的发送方式, 又需要大改MessageService源码;
// 如果遵循开闭原则设计, 会怎么样呢?
看看 good demo:
////////////////////////////////////////////////////////////////
/**
* 信息发送接口
*
* @version 0.1
* @author xy
* @date 2018年1月23日 下午11:02:30
*/
public interface ISendable {
void send();
}
// 各个信息发送类实现接口
public class MessageSend implements ISendable{}
public class EmailMessageSend implements ISendable{}
// messageService以后可以一直不变
public class MessageService {
private ISendable sender;// 这是典型的组合优于继承;
public MessageService(ISendable sender) {
this.sender = messageHelper;
}
public void send(String msg) {
sender.send(msg);
}
}
// 客户端调用
public class MessageClient {
public static void main(String[] args) {
MessageService messageService = new MessageService(new MessageSend());
messageService.send("Merry Christmas !");
MessageService messageService2 = new MessageService(new EmailMessageSend());
messageService2.send("Merry Christmas !");
}
}
// 对于MessageService服务类来说,不用做任何修改,只需要扩展新的推送消息的工具类即可, 这就是遵循开闭原则的好处
单一职责原则
也可理解为单一职责原则, 一个类或方法,只负责单一功
解决这种问题: 假如有类Class1完成职责T1,T2,当职责T1或T2有变更需要修改时,有可能影响到该类的另外一个职责正常工作。
错误示例:
public class Calculator {
public int add() throws NumberFormatException, IOException{
//这里读取和加法耦合在一起了,如果需要增加业务方法【减法】,就要更改代码
File file = new File("E:/data.txt");
BufferedReader br = new BufferedReader(new FileReader(file));
int a = Integer.valueOf(br.readLine());
int b = Integer.valueOf(br.readLine());
return a+b;
}
public static void main(String[] args) throws NumberFormatException, IOException {
Calculator calculator = new Calculator();
System.out.println("result:" + calculator.add());
}
}
遵循单一职责原则后, 是这样: 分离出来一个类用来读取数据,将一个类拆成了两个类,这样以后我们如果有减法,乘法等等,就不用出现那么多重复代码了
public class Reader {
int a,b;
public Reader(String path) throws NumberFormatException, IOException{
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
a = Integer.valueOf(br.readLine());
b = Integer.valueOf(br.readLine());
}
public int getA(){
return a;
}
public int getB(){
return b;
}
}
//单独的计算类
public class Calculator {
public int add(int a,int b){
return a + b;
}
public static void main(String[] args) throws NumberFormatException, IOException {
Reader reader = new Reader("E:/data.txt");
Calculator calculator = new Calculator();
System.out.println("result:" + calculator.add(reader.getA(),reader.getB()));
}
}
里氏替换原则 (是否使用需要权衡)
里氏代换原则中说,所有引用基类的地方必须能透明地替换为使用其子类的对象, 是针对继承
这一特性提出的(继承时最好遵循里氏替换)
更直接的理解是: 子类一般不会重写父类的方法
就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。
错误示例:
/* 父类 */
public class Parent {
public void method(){
System.out.println("parent method");
}
}
/* 子类 */
public class SubClass extends Parent{
//结果某一个子类重写了父类的方法,说不支持该操作了
public void method() {
throw new UnsupportedOperationException();
}
}
//某一个类
public class SomeoneClass {
//有某一个方法,使用了一个父类类型
public void someoneMethod(Parent parent){
parent.method();
}
}
/* 测试类 */
public class Client {
public static void main(String[] args) {
SomeoneClass someoneClass = new SomeoneClass();
someoneClass.someoneMethod(new Parent());
someoneClass.someoneMethod(new SubClass());//此处报错, 运行时才产生报错 // 破坏了继承体系
}
}
接口隔离原则
接口最小化原则,保证接口内的方法尽可能的最少; 使用多个隔离的接口,比使用单个接口要好
解决这种问题: 类A通过接口interface依赖类B,类C通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
//这是错误示例,当实现此接口 时,必须实现不必要的方法 other()
public interface Mobile {
public void call();//手机可以打电话
public void sendMessage();//手机可以发短信
public void game();//手机可以玩愤怒的小鸟?这是不合适的方法
}
//正确的方式是:新建一个接口来拓展Mobile接口
public interface SmartPhone extends Mobile{
public void game();//智能手机的接口就可以加入这个方法了
}
依赖倒置原则
即实现都是易变的,而只有接口是稳定的,所以当依赖于接口时,实现的变化并不会影响客户端的调用。
就像常说的面向接口编程
, 底层注入高层而不是高层依赖底层;
解决这种问题: 类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成, 这明显不符合"开闭原则"; 将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率
//实现加法,抽象出一个接口,具体的实现类分别实现从数据库中读取、从文件系统读取,从XML文件读取
public interface Reader {
public int getA();
public int getB();
}
spring ioc 就是 依赖倒置的思想 (https://www.zhihu.com/question/23277575/answer/169698662)
迪米特原则(常用)
也称最小知识原则, 即一个类中不要出现无关的类, 如出现连续的get方法 getXXX().getXXX().getXXX()
就可能违反了迪米特法则
比如类A的方法需要借助类B完成, 类B又需要借助类C中的方法完成
错误示例:
public class A {
public String name;
public A(String name) {
this.name = name;
}
public B getB(String name) {
return new B(name);
}
public void work() {
B b = getB("李四");
C c = b.getC("王五");// A 和C耦合了
c.work();
}
}
public class B {
private String name;
public B(String name) {
this.name = name;
}
public C getC(String name) {
return new C(name);
}
}
public class C {
public String name;
public C(String name) {
this.name = name;
}
public void work() {
System.out.println(name + "把这件事做好了");
}
}
public class Client {
public static void main(String[] args) {
A a = new A("张三");
a.work();
}
}
正确的改进:
public class A {
public String name;
public A(String name) {
this.name = name;
}
public B getB(String name) {
return new B(name);
}
public void work() {
B b = getB("李四");
b.work();
}
}
public class B {
private String name;
public B(String name) {
this.name = name;
}
public C getC(String name) {
return new C(name);
}
public void work(){
C c = getC("王五");
c.work();
}
}
类 C 不变