七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码
一、Open-Closed Principle开闭原则
(一)、开闭原则定义
开闭原则是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架,用实现扩展细节。可以提高软件系统的可复用性以及可维护性。
开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立文档灵活的系统,例如:我们进行版本更新时,尽可能不修改源代码,但是可以增加新功能。
开闭原则就是在我们需要发生变化时,应该是进行代码的拓展,而不是进行修改源码。实现开闭原则的核心思想就是面向抽象编程。
(二)、开闭原则优点
(1)、可复用性高:灵活的增加新功能
(2)、可维护性高:不需要修改底层源码,只需要对相应功能进行更新
(三)、案例代码
1、书籍接口代码
public interface Book {
public String getName();
public String getAuthor();
public Double getPrice();
}
2、Java类书籍
public class JavaBook implements Book{
private String name;
private String author;
private Double price;
public JavaBook(String name, String author, Double price) {
this.name = name;
this.author = author;
this.price = price;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getAuthor() {
return this.author;
}
@Override
public Double getPrice() {
return this.price;
}
}
3、Java类折扣书籍
当我们需要折扣出售书籍时,我们并非在JavaBook的基础上修改getPrice方法中的源码,这样会导致我们无法获得原价,此时我们需要进行添加多一个方法,并且由于以后折扣出售会有不同的处理方案,所以我们应选择拓展这个类。
public class JavaDiscountPrice extends JavaBook{
public JavaDiscountPrice(String name, String author, Double price) {
super(name, author, price);
}
public Double getJavaDiscountPrice() {
return super.getPrice()*0.5;
}
}
4、增加JavaDiscountPrice类的前后区别
public class Main {
public static void main(String[] args) {
// 直接在JavaBook上进行修改
JavaBook javaBook = new JavaBook("java书籍", "无名氏", 12.00);
// 只可得知折扣后的价格
System.out.println("折扣" + javaBook.getPrice());
// 添加JavaDiscountPrice
JavaDiscountPrice javaDiscountPrice = new JavaDiscountPrice("java书籍", "无名氏", 12.00);
// 可得知原价以及折扣后的价格
System.out.println("原价:" + javaBook.getPrice());
System.out.println("折扣:" + javaDiscountPrice.getPrice());
}
}
二、Dependence Inversion Principle依赖倒置原则
(一)、依赖倒置原则定义
依赖倒置原则是指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节;细节应该依赖抽象。
通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。
(二)、依赖倒置原则优点
(1)、可以减少类间的耦合性。
(2)、提高代码的可读性和可维护性,降低修改代码所带来的风险。
(三)、案例代码
1、不满足依赖倒置原则的情况
随着顾客打算购买的书籍的增多或者变化,我们将不断增加或改变底层代码,同时在高层也需要追加调用,这样导致我们的代码越来越臃肿,同时也难以维护,实际上也是非常不稳定的,在修改代码的同时会带来意想不到的风险。
public class Customer {
public void getJavaBook() {
System.out.println("购买了java语言书");
}
public void getCBook() {
System.out.println("购买了C语言书");
}
public void getPythonBook() {
System.out.println("购买了Python语言书");
}
public static void main(String[] args) {
Customer customer = new Customer();
customer.getCBook();
customer.getJavaBook();
customer.getPythonBook();
}
}
2、接口传递方式进行依赖注入
(1)、书籍抽象接口类
public interface Book {
void getBook();
}
(2)、Java类书籍实现类
public class JavaBook implements Book{
@Override
public void getBook() {
System.out.println("购买了Java类书籍");
}
}
(3)、Python类书籍实现类
public class PythonBook implements Book{
@Override
public void getBook() {
System.out.println("购买了Python类书籍");
}
}
(4)、接口传递方式调用
public class PurchaseBook {
private Book book;
public void getBook(Book book){
book.getBook();
}
public static void main(String[] args) {
PurchaseBook purchaseBook = new PurchaseBook();
purchaseBook.getBook(new JavaBook());
purchaseBook.getBook(new PythonBook());
}
}
3、构造器方式进行依赖注入
public class PurchaseBook {
private Book book;
PurchaseBook(Book book) {
this.book = book;
}
public void getBook() {
book.getBook();
}
public static void main(String[] args) {
PurchaseBook purchaseBook = new PurchaseBook(new JavaBook());
purchaseBook.getBook();
}
}
4、set方式进行依赖注入
public class PurchaseBook {
private Book book;
public void setBook(Book book){
this.book = book;
}
public void getBook(){
book.getBook();
}
public static void main(String[] args) {
PurchaseBook purchaseBook = new PurchaseBook();
purchaseBook.setBook(new JavaBook());
purchaseBook.getBook();
}
}
三、 Simple Responsibility Principle单一职责原则
(一)、单一职责原则定义
单一职责原则是指不要存在多一个一个导致类变更的原因。无论是接口、方法、或者类,我们在编写的时候都有可能会涉及到许多的业务逻辑,我们应该尽量将业务逻辑一致的放在一起,其他的进行分开,这样子就不会使一个接口、方法、类去处理多个职责,而在发生代码变更的时候相互影响。
总体来说就是一个Class/Interface/Method只负责一项职责。
(二)、单一职责原则优点
(1)、降低类的功能复杂度
(2)、提高系统的可维护性
(3)、变更风险低
(三)、案例代码
书的进货和卖出是两种业务,所以分成两个接口,后续业务扩展由两类接口伸展
// 进货
public interface EnterBook {
void enterBook();
}
// 卖货
public interface SellingBook {
void sellingBook();
}
四、Interface Segregation Principle接口隔离原则
(一)、接口隔离原则定义
接口隔离原则是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖他不需要的接口。
-
一个类对一类的依赖应该建立在最小的接口之上
-
建立单一接口不要建立庞大臃肿的接口
-
尽量细化接口,接口中的方法尽量少
(二)、接口隔离原则优点
(1)、符合高内聚,低耦合
(2)、具有很好的可读性、可扩展性和可维护性
(三)、案例代码
1、不满足接口隔离原则的情况
Animal接口:一个动物行为接口,包含吃、飞以及游泳行为
public interface Animal {
void eat();
void fly();
void swim();
}
Bird类实现:
public class Bird implements Animal{
@Override
public void eat() {
}
@Override
public void fly() {
}
@Override
public void swim() {
}
}
Dog类实现:
public class Dog implements Animal{
@Override
public void eat() {
}
@Override
public void fly() {
}
@Override
public void swim() {
}
}
此时可发现,Bird类的swim方法只可空置,同样Dog类的fly方法也只可空置。
2、符合接口隔离原则的情况
EatAnimal接口:动物吃行为
public interface EatAnimal {
void eat();
}
FlyAnimal接口:动物飞行为
public interface FlyAnimal {
void fly();
}
SwimAnimal接口:动物游泳行为
public interface SwimAnimal {
void swim();
}
Dog类只实现EatAnimal和SwimAnimal接口:
public class Dog implements SwimAnimal,EatAnimal{
@Override
public void eat() {
}
@Override
public void swim() {
}
}
五、Law of Demeter 迪米特法则
(一)、迪米特原则定义
迪米特原则是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则。尽量降低类与类之间的耦合。迪米特原则主要强调只和朋友交流,不和陌生人说话。
迪米特原则中的朋友是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
(二)、迪米特原则优点
(1)、降低了类之间的耦合度,提高了模块的相对独立性。
(2)、由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
(三)、案例代码
1、不符合迪米特原则的情况
此时A不认识C只认识B,而B认识C。虽然实现了play(),但是不符合迪米特原则
public class A {
// A认识B,但是不认识C;而B认识C。
B b = new B();
public void play() {
System.out.println("play");
b.play();
C c = new C();
c.play();
}
public static void main(String[] args) {
A a = new A();
a.play();
}
}
2、符合迪米特原则的情况
因为A认识B,所以只同B联系
public class A {
B b = new B();
public void play() {
System.out.println("Aplay");
b.play();
}
public static void main(String[] args) {
A a = new A();
a.play();
}
}
因为B认识C,所以通过B来联系C,而不是通过A直接联系C
public class B {
C c = new C();
public void play() {
System.out.println("Bplay");
c.play();
}
}
六、Liskov Substitution Principle里氏替换原则
(一)、里氏替换原则定义
里氏替换原则是指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
简单来说便是,继续必须确保超类所拥有的性质在子类中仍然成立。
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
- 子类中可以增加自己特有的方法。
- 当子类重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
(二)、里氏替换原则优点
(1)、里氏替换原则是实现开闭原则的重要方式之一。
(2)、它克服了继承中重写父类造成的可复用性变差的缺点。
(3)、它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
(三)、案例代码
1、不符合里氏替换原则的情况
长方形的类:
public class Rectangle {
private long width;
private long height;
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
public long getHeight() {
return height;
}
public void setHeight(long height) {
this.height = height;
}
}
正方形的类:继承了长方形,并且改变了长方形的方法
public class Squre extends Rectangle {
private long length;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
@Override
public long getWidth() {
return length;
}
@Override
public void setWidth(long width) {
setLength(width);
}
@Override
public long getHeight() {
return length;
}
@Override
public void setHeight(long height) {
setLength(height);
}
}
错误情况:检查长方形的长是否大于宽,如果没有就需要加1直到长大于宽,但是因为正方形是长方形的子类,所以传正方形的类依然可以成立,但是方法执行是死循环因为长和宽永远相等。
public class Check {
public void resize(Rectangle rectangle){
while(rectangle.getWidth()>=rectangle.getHeight()){
rectangle.setHeight(rectangle.getHeight()+1);
System.out.println("长"+rectangle.getHeight()+"宽"+rectangle.getWidth());
}
System.out.println("长"+rectangle.getHeight()+"宽"+rectangle.getWidth());
}
public static void main(String[] args) {
Squre squre = new Squre();
squre.setLength(10);
Check check = new Check();
check.resize(squre);
}
}
2、里氏替换原则的方法的前置条件的范围不同的区别
当子类重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。可以自己通过写代码验证
你会发现执行结果依然是父类的,但一旦你的前置条件比父类方法更严格就相当于是子类的方法,执行的方法不同。子类可以扩展方法但不得重写或覆盖已经实现的方法
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。这个当你用代码实现的时候如果不满足此条件编译器都会报错。
public class Parent {
public void method(HashMap hashMap){
System.out.println("父类方法执行");
}
}
public class Son extends Parent{
public void method(Map hashMap) {
System.out.println("子类方法执行");
}
public static void main(String[] args) {
Son son = new Son();
HashMap hashMap = new HashMap();
son.method(hashMap);
}
}
七、Composite&Aggregate Reuse Principle合成复用原则
(一)、合成复用原则定义
合成复用原则是指尽量使用对象组合(has-a)/聚合(contanis-a),而不是继承关系达到软件复用的目的,可以使得系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
继承称之为白箱复用,相当于把所有的细节都暴露给子类。组合/聚合称之为黑箱复用,对类之外的对象是无法获取到细节的。
(二)、合成复用原则优点
(1)、它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
(2)、新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
(3)、复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
(三)、案例代码
public interface DBconnect {
String getConnect();
}
public class MysqlConnect implements DBconnect {
public String getConnect() {
return "mysql连接";
}
}
public class ProductConnect {
private DBconnect dBconnect ;
public void setdBconnect(DBconnect dBconnect) {
this.dBconnect = dBconnect;
}
public void addConnect(){
System.out.println(dBconnect.getConnect());
}
public static void main(String[] args) {
ProductConnect productConnect = new ProductConnect();
productConnect.setdBconnect(new MysqlConnect());
productConnect.addConnect();
}
}
八、设计模式的相关博客文章链接
1、工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂)
链接: 工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂).
2、单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式)
链接: 单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式).
3、原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析)
链接: 原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析).