软件设计原则概述
学习设计原则是学习设计模式的基础。
实际开发过程中,并不一定要求所有代码都遵循设计原则,只需要在适当的场景遵循设计原则,就可以帮助开发者设计出更加优雅的代码结构。
软件设计原则一共有七个:
开闭原则:对扩展开放,对修改关闭。
依赖倒置原则:通过抽象使各个类或者模块不相互影响,实现松耦合。
单一职责原则:一个类、接口、方法只做一件事。
接口隔离原则:尽量保证接口的纯洁性,客户端不应该依赖不需要的接口。
迪米特法则:又叫最少知道原则,一个类对其所依赖的类知道得越少越好。
里氏替换原则:子类可以扩展父类的功能但不能改变父类原有的功能。
合成复用原则:尽量使用对象组合、聚合,而不使用继承关系达到代码复用的目的。
具体详细参考:软件设计原则思维导图
1.开闭原则
开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
实现开闭原则的核心思想就是面向抽象编程。强调用抽象构建框架,用实现扩展细节,提高软件系统的可复用性及可维护性。
假设动物都有跑的行为
public interface Animal {
void run();
}
public class Dog implements Animal{
@Override
public void run() {
System.out.println("Dog is running");
}
}
若此时要给Dog添加Run的修饰,在不修改原有代码前提前下,可根据开闭原则,用拓展的方式实现
public class NewDog extends Dog{
public void newRun(){
System.out.println("NewDog is happy running");
}
}
2.依赖倒置原则
依赖倒置原则(Dependence Inversion Principle,DIP)是程序要依赖于抽象接口,不要依赖于具体实现。
高层模块不应该依赖底层模块,二者都应该依赖其抽象。
通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。
汽车工厂生产BM和BC
public class CarFactory {
void generateBM(){
System.out.println("CarFactory is generateBM");
}
void generateBC(){
System.out.println("CarFactory is generateBC");
}
}
public static void main(String[] args) {
CarFactory carFactory = new CarFactory();
carFactory.generateBM();
carFactory.generateBC();
}
若此时汽车工厂还想生成AD,则除了在CarFactory
类(低层)添加generateAD()
方法,还需要在调用的地方(高层)调用。
解决方案:创建抽象接口,不同的车型实现相同接口
public interface IGenerate {
void generate();
}
public class BM implements IGenerate{
@Override
public void generate() {
System.out.println("CarFactory is generateBM");
}
}
public class BC implements IGenerate{
@Override
public void generate() {
System.out.println("CarFactory is generateBC");
}
}
public class AD implements IGenerate{
@Override
public void generate() {
System.out.println("CarFactory is generateAD");
}
}
这是一种常用的方式,叫依赖注入。注入的方式还有构造器方式和 setter 方式
public class CarFactory {
void generate(IGenerate generate){
generate.generate();
}
}
public static void main(String[] args) {
CarFactory carFactory = new CarFactory();
carFactory.generate(new BM());
carFactory.generate(new BC());
carFactory.generate(new AD());
}
-------------------------------------------------------------------------------------------
public class CarFactory2 {
private static IGenerate generate;
public CarFactory2(IGenerate iGenerate) {
this.generate = iGenerate;
}
public void generate() {
generate.generate();
}
public static void main(String[] args) {
CarFactory2 bm = new CarFactory2(new BM());
bm.generate();
CarFactory2 bc = new CarFactory2(new BC());
bc.generate();
CarFactory2 ad = new CarFactory2(new AD());
ad.generate();
}
}
-------------------------------------------------------------------------------------------
public class CarFactory3 {
private static IGenerate generate;
public static void setGenerate(IGenerate generate) {
CarFactory3.generate = generate;
}
public void generate() {
generate.generate();
}
public static void main(String[] args) {
BM bm = new BM();
CarFactory3 bmFc = new CarFactory3();
bmFc.setGenerate(bm);
bmFc.generate();
BC bc = new BC();
CarFactory3 bcFc = new CarFactory3();
bcFc.setGenerate(bc);
bcFc.generate();
AD ad = new AD();
CarFactory3 adFc = new CarFactory3();
adFc.setGenerate(ad);
adFc.generate();
}
}
3.单一职责原则
单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。
比如一个 Class负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。于是可以给两个职责分别用两个 Class 来实现,进行解耦,即使以后需求变更维护也互不影响。
这样做可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。
单一职责原则就是一个
Class/Interface/Method
只负责一项职责。
CarFactory类既要生成small Car又要生成big Car,承担了两种处理逻辑
public class CarFactory {
void generate(Integer type) {
if (type == 1) {
System.out.println("generate small Car");
} else {
System.out.println("generate big Car");
}
}
public static void main(String[] args) {
CarFactory carFactory = new CarFactory();
carFactory.generate(1);
carFactory.generate(2);
}
}
此时,若要生产不同类型Car,则需修改代码,造成不可控的风险。
解决方案:对职责进行分离解耦
public class SmallCar {
void generate() {
System.out.println("generate small Car");
}
}
public class BigCar {
void generate() {
System.out.println("generate big Car");
}
}
public static void main(String[] args) {
SmallCar smallCar = new SmallCar();
smallCar.generate();
BigCar bigCar = new BigCar();
bigCar.generate();
}
4.接口隔离原则
接口隔离原则(Interface Segregation Principle, ISP)是指使用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
接口隔离原则符合常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。
注意点:
1.一个类对一类的依赖应该建立在最小的接口之上
2.建立单一接口,不要建立庞大臃肿的接口
3.尽量细化接口,接口中的方法尽量少(要适度)
Bird不具备run的行为,Dog不具备fly的行为
public interface IAnimal {
void eat();
void fly();
void run();
}
public class Bird implements IAnimal {
@Override
public void eat() {}
@Override
public void fly() {}
@Override
public void run() {}
}
public class Dog implements IAnimal {
@Override
public void eat() {
}
@Override
public void fly() {
}
@Override
public void run() {
}
}
此时,应该针对不同动物行为来设计不同的接口
public interface IEatAnimal {
void eat();
}
public interface IFlyAnimal {
void fly();
}
public interface IRunAnimal {
void run();
}
不同的动物选择不同的接口实现,从而达到接口隔离原则,符合高内聚低耦合的设计思想
public class Dog implements IRunAnimal,IEatAnimal {
@Override
public void eat() {}
@Override
public void run() {}
}
public class Bird implements IFlyAnimal,IEatAnimal {
@Override
public void eat() {}
@Override
public void fly() {}
}
5.迪米特法则
迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合。主要强调只和朋友交流,不和陌生人说话。
朋友的定义:
出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类
陌生人的定义:
出现在方法体内部的类不属于朋友类
举例:校长需要知道学生的信息,就需要由老师进行统计报告
Principal类做了Teacher类做的事情,与Student 产生关联关系
class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
class Teacher {
/**
* 报告学生信息
*/
void report(List<Student> studentLists) {
studentLists.stream().forEach(student -> {
System.out.println("name :" + student.name + " age : " + student.age);
});
}
}
class Principal {
void statistics(Teacher teacher) {
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("张三", 18));
students.add(new Student("李四", 19));
teacher.report(students);
}
public static void main(String[] args) {
Principal principal = new Principal();
Teacher teacher = new Teacher();
principal.statistics(teacher);
}
}
Principal类只关心Teacher类反馈的结果,不需要与Student产生关系
class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
class Teacher {
/**
* 报告学生信息
*/
void report() {
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("张三", 18));
students.add(new Student("李四", 19));
students.stream().forEach(student -> {
System.out.println("name :" + student.name + " age : " + student.age);
});
}
}
class Principal {
void statistics(Teacher teacher) {
teacher.report();
}
public static void main(String[] args) {
Principal principal = new Principal();
Teacher teacher = new Teacher();
principal.statistics(teacher);
}
}
6.里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)是指一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
注意点:
1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
2.子类中可以增加自己特有的方法
3.当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松
4.当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等
假设动物都有跑的行为,后来需要对该行为进行修饰,进行了重新父类方法
public interface Animal {
void run();
}
public class Dog implements Animal{
@Override
public void run() {
System.out.println("Dog is running");
}
}
public class NewDog extends Dog{
public void run(){
System.out.println("NewDog is happy running");
}
}
正确的做法是增加方法而不是覆盖父类的方法
public class NewDog extends Dog{
public void newRun(){
System.out.println("NewDog is happy running");
}
}
7.合成复用原则
合成复用原则(Composite/Aggregate Reuse Principle,CARP)是指尽量使用对象组合(has-a)或聚合(contanis-a),而不是继承关系达到软件复用的目的。
可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
继承又叫白箱复用,相当于把所有的实现细节暴露给子类
组合/聚合也称为黑箱复用,对类以外的对象是无法获取到实现细节的
使用继承
public class DBConnection {
public String getConnection(){
return "connection";
}
}
public class MyLDao extends DBConnection {
public void save(Object obj) {
String connection = super.getConnection();
System.out.println("获取连接:" + connection + " ,进行保存");
}
}
改造为合成复用原则。使用构造注入,也可以使用Setter注入
public class DBConnection {
public String getConnection(){
return "connection";
}
}
public class MyLDao {
private DBConnection dbConnection;
public MyLDao (DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void save(Object obj) {
String connection = dbConnection.getConnection();
System.out.println("获取连接:" + connection + " ,进行保存");
}
}
public static void main(String[] args) {
MyLDao myLDao = new MyLDao(new DBConnection());
myLDao.save(new Object());
}
-----------------------------------------------------------------------------
public class MyLDao {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void save(Object obj) {
String connection = dbConnection.getConnection();
System.out.println("获取连接:" + connection + " ,进行保存");
}
}
public static void main(String[] args) {
MyLDao myLDao = new MyLDao();
myLDao.setDbConnection(new DBConnection());
myLDao.save(new Object());
}
假如业务变化,要求使用Oracle,可以创建新的Oracle数据库连接继承原有连接,原有代码无须进行修改,而且还可以很灵活地增加新的数据库连接方式。
public class OracleDBConnection extends DBConnection {
public String getConnection() {
return "Oracle Connection";
}
}
public class MyLDao {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void save(Object obj) {
String connection = dbConnection.getConnection();
System.out.println("获取连接:" + connection + " ,进行保存");
}
}
public static void main(String[] args) {
MyLDao myLDao = new MyLDao();
myLDao.setDbConnection(new OracleDBConnection());
myLDao.save(new Object());
}
其实DBConnection还不是一种抽象,不便于系统扩展,在设计之初可将DBConnection变成抽象类,若有新的需求只需要实现具体逻辑。
public abstract class DBConnection {
public abstract String getConnection();
}
public class MySQLConnection extends DBConnection {
@Override
public String getConnection() {
return "MySQL";
}
}
public class OracleConnection extends DBConnection {
@Override
public String getConnection() {
return "Oracle";
}
}
public class MyLDao {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void save(Object obj) {
String connection = dbConnection.getConnection();
System.out.println("获取连接:" + connection + " ,进行保存");
}
}
public static void main(String[] args) {
MyLDao myLDao = new MyLDao();
myLDao.setDbConnection(new OracleConnection());
myLDao.save(new Object());
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136917.html