7大设计原则是JAVA老前辈们多年经验的总结,掌握并能灵活运用到实际工作中可以帮助我们解决很多实际问题。
7大设计原则:
开闭原则:
在面向对象的开发过程中,一个类、模块应该面向扩展开放、面向修改关闭。强调用抽象来构建框架,用实现来扩展细节。
举例说明:现在有一个水果的接口。
public interface Fruit{
Double getFriutPrice(String fruitName);
Double getFruitSize(String fruitName);
}
但是水果有很多种;有桃子、梨子、橘子等,每种果树都有自己的价格和形状大小
以桃子为例:实现水果接口,增加了一个price属性
public class Peach implments Fruit{
private String productArea;
private Double price;
private Double size;
public Peach(Double price,Double size,String productArea) {
this.price = price;
this.size = size;
this.productArea = productArea;
}
public Double getPrice(){
return this.price;
}
public Double getSize(){
return this.size;
}
public Double getProductArea(){
return this.productArea;
}
}
假如此时我们要对桃子进行促销,显然直接去修改Peach的价格是不合适的,那怎么办呢?我们可以用一个新的促销类来继承Peach,重写其price方法,这样就保证了原来调用Peach的地方不受影响。
public class SalesPeach extends Peach{
private static final Double sales_Factor = 0.75d;
public Peach(Double price,Double size,String productArea) {
super(price,size,productArea);
}
public Double getPrice(){
return super.getPrice() * sales_Factor;
}
}
依赖倒置原则:
高层次模块不应该依赖低层次模块,高层次模块和低层次模块都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
进而减少模块之间的相互依赖(耦合),从而提高系统的稳定性和可维护性。
举例说明;
大学生选课,该学生特别热爱java课程,所以就选了java课程,当该学生的学习热情被点燃,想选择大数据课程的时候,我们就需要在Student中增加一个方法 void chooseBidDataCourse();这样是非常不稳定的,这样的修改风险也较高。
public class Student{
void chooseJavaCource();
}
依据依赖倒置原则,我们来对其进行改造:
首先,提供一个抽象:Course接口
public interface Course {
void choose();
}
提供接口的多个实现JavaCourse和BigDataCourse
public class JavaCourse implements Course {
void choose() {
System.out.println("选择了java课程。");
}
}
public class BigDataCourse implements Course {
void choose() {
System.out.println("选择了大数据课程。");
}
}
public class Student{
void chooseCource(Course course) {
course.choose();
}
}
或
public class Student{
private Course course;
public Student(Course course) {
this.course = course;
}
void chooseCource() {
course.choose();
}
}
这样即使增加再多的课程,只要新增一个类实现课程接口,然后将课程传给Student,就可以实现选课。
使用抽象搭建起来的架构要比使用实现细节搭建起来的架构稳定的多,所以鼓励大家面向接口编程,先抽象再细节。
单一职责原则:
不要存在多于一个导致类变更的原因,假设我们有一个类有两个职责,那么当业务改变,需要修改其中一个职责的时候,有可能会影响到另一个职责,导致其故障。怎么解决呢?我们可以将这个类拆分成两个类,每个类负责一个职责,这样就不会相互影响了。总的来说:就是一个Class、Interface、Method最好只有一个职责。
接口隔离原则;
主要是针对接口编写时,接口应该尽可能小,应该使用多个专用接口而不是使用一个总接口。一个客户端不应该依赖它用不到的接口。
实际开发过程中,应注意;
1.一个类对另一个类的依赖应该建立在最小接口之上。
2.不要设计过于臃肿的接口,应建立单一接口
3.尽量细化接口
在实际开发过程中,应该多花时间去思考,根据具体的业务模型,设计对应的抽象,并且应该对抽象中可变的部分进行预判,为后续扩展留有余地。
举例说明;
以动物举例,设计一个如下的接口,接口里面定义进食,飞翔、游泳方法。
public interface Animal {
void eat();
void fly();
void swim();
}
然后写接口的实现类
public class Fish implements Animal {
void eat(){
System.out.print("鱼儿进食");
}
void fly(){}
void swim(){
System.out.print("鱼儿游泳");
}
}
public class Bird implements Animal {
void eat(){
System.out.print("鸟儿进食");
}
void fly(){
System.out.print("鸟儿飞翔");
}
void swim(){
}
}
public class Dog implements Animal {
void eat(){
System.out.print("狗子进食");
}
void fly(){
}
void swim(){
}
}
我们发现鱼儿是不能飞的,所以fly()方法是空着的,鸟儿是不能游泳的,所以swim方法空着,狗子是不能飞和游泳的,所以这两个方法空着。重接口带来的问题很明显,依赖了不该依赖的抽象。
如果我们把Animal接口拆分:
public interface EatAnimal {
void eat();
}
public interface FlyAnimal {
void fly();
}
public interface SwimAnimal {
void swim();
}
这时我们再来实现接口:
public class Fish implements EatAnimal,SwimAnimal {
void eat(){
System.out.print("鱼儿进食");
}
void swim(){
System.out.print("鱼儿游泳");
}
}
public class Bird implements EatAnimal,FlyAnimal {
void eat(){
System.out.print("鸟儿进食");
}
void fly(){
System.out.print("鸟儿飞翔");
}
}
public class Dog implements EatAnimal {
void eat(){
System.out.print("狗子进食");
}
}
这样拆分接口以后,我们可以对要实现的接口进行自由组合实现不同的业务。代码的耦合度降低了,代码的可读性和可维护性也大大提升。
迪米特原则:
又叫最少知道原则,即一个类应该尽可能少的知道其他类的细节,也可以理解成一个类应该跟尽量少的类产生关系。
形象点说:就是不要和陌生人说话,一个类的属性和构造参数中出现的类都是朋友类,只在方法中出现的类就是陌生类。
举例说明:现在设计一个权限系统,老板想要知道目前商品的销售额,老板找到销售总监,让他去统计销售额。
public class ProductSale {
private Double sales;
private String productName;
public Double getSales() {
return this.sales;
}
public Double getProductName() {
return this.productName;
}
}
public class SalesManager {
public Double calSales(List<ProductSale> ProductSale) {
Double sale = 0.0;
for(ProductSale productSale:ProductSale) {
System.out.print("product:" + productSale.getProductName()
+ "'s sales is " + productSale.getSales());
sale += productSale.getSales();
}
System.out.print("sales is " + productSale.getSales());
return sale;
}
}
public class Boss {
public void getSalesData(SalesManager manager) {
// 模拟老板一页一页翻页,销售总监实时统计
List<ProductSale> sales = new ArrayList();
manager.calSales();
}
}
其中老板既要管理项目总监,又要关心产品销售数据,可谓是身心俱疲,迫切需要对目前的状况进行改进:销售数据只要总监关心就好了,老板只要一个结果。
public class ProductSale {
private Double sales;
private String productName;
public Double getSales() {
return this.sales;
}
public Double getProductName() {
return this.productName;
}
}
public class SalesManager {
public Double calSales() {
// 总监一页一页翻页,实时统计每一页的金额
List<ProductSale> sales = new ArrayList();
Double sale = 0.0;
for(ProductSale productSale:ProductSale) {
System.out.print("product:" + productSale.getProductName()
+ "'s sales is " + productSale.getSales());
sale += productSale.getSales();
}
System.out.print("sales is " + productSale.getSales());
return sale;
}
}
public class Boss {
public void getSalesData(SalesManager manager) {
manager.calSales();
}
}
里氏替换原则:
意思就是说;程序中可以使用父类的地方,一定也可以使用子类,即子类可以替换掉父类而不影响程序的正常运行。
子类继承父类的几个特点;
子类可以扩展父类的功能,而不能改变父类原有的功能
合成复用原则:
指应该尽量使用聚合或组合的方式而不是利用继承的方式来达到代码复用的目的。这样设计可降低代码之间的耦合性,一个类的修改造成的影响也更小。
举例说明;我们以数据库连接和DAO操作为例来说明
public class DBConnection{
public static String getConnection(){
System.out.println("使用MYSQL数据库创建连接");
return "Mysql";
}
}
public class UserDAO {
public void queryUser() {
DBConnection.getConnection();
System.out.println("使用MYSQL数据库连接进行查询");
}
}
当我们数据源改变,我们要用oracle数据库,那我们只能在DBConnection中增加创建Oracle数据库连接的方法了,这样是违背开闭原则的,那怎么办?我们可以将getConnection定义为一个抽象方法。
public abstract class DBConnection{
public abstract String getConnection();
}
public class MysqlConnection extends DBConnection{
public String getConnection(){
System.out.println("使用MYSQL数据库创建连接");
return "Mysql";
}
}
public class OracleConnection extends DBConnection{
public String getConnection(){
System.out.println("使用Oracle数据库创建连接");
return "Oracle";
}
}
public class UserDAO {
public void queryUser(DBConnection connection) {
String dbconn = connection.getConnection();
System.out.println("使用" + dbconn + "数据库连接进行查询");
}
}
这样,我们在调用DAO的时候,只要传入对应的实现子类,就可以获取到对应的数据库连接进行查询了。