面向对象编程时,尽可能遵守SOLID这五大原则。 在开发中,SOLID 能减少代码的耦合性,增加灵活性、可扩展性、可复用性,让我们的代码更健壮。系统开发从零开始构建时,分析系统整个原型,每个模块进行寻找它的边界,部分进行分离出抽象的部分,隔离封装. 设计的时候,接口粒度要小, 层层分割,抽离封装。
Single Responsibility Principle职责单一原则 (SRP)
This principle states that “ A class should have only one reason to change ” which means every class should have a single responsibility or single job or single purpose.
如果一个类需要修改,其原因只有一个!这一个类只能允许 ----- Do One thing !!并不是指类只能有一种方法,是指类中所有的函数封装的方法的职责只承担其一项功能任务的责任。只有这样的设计模式,它才能松散obejects之间的紧密耦合性。 请看下方的示范性错误代码!它就属于违反了职责的单一性!
// Customer 类
public class Customer {
String name;
int age;
long bill;
List- listsOfItems;
Customer(String name,int age){
this.name=name;
this.age=age;
}
// Calculate bill should not be responsibility of customer
public long calculateBill(long tax){
for (Item item:listsOfItems) {
bill+=item.getPrice();
}
bill+=tax;
this.setBill(bill);
return bill;
}
//Report generation should not be responsibility of customer
public void generateReport(String reportType){
if(reportType.equalsIgnoreCase("CSV")){
System.out.println("Generate CSV report");
}
if(reportType.equalsIgnoreCase("XML")){
System.out.println("Generate XML report");
}
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public long getBill() { return bill;}
public void setBill(long bill) { this.bill = bill; }
public List
- getListsOfItems() { return listsOfItems; }
public void setListsOfItems(List
- listsOfItems) { this.listsOfItems = listsOfItems; }
}
以上是示范违背了SRP原则的代码块, 应该以2个类的形式承载类单一的功能 ,对上方代码进行仅仅只负责它自身单一功能的封装成两个类,一个是BillCalculator 、 另一个则是ReportGenerator。
// Calculator Bill Functionality
public class BillCalculator {
public long calculateBill(Customer customer,long tax){
long bill=0;
List listsOfItems=customer.getListsOfItems();
for (Item item:listsOfItems) {
bill+=item.getPrice();
}
bill+=tax;
customer.setBill(bill);
return bill;
}
}
// Report Generation Functionality
public class ReportGenerator {
public void generateReport(Customer customer,String reportType){
// Extract data from customer object and generate the report
if(reportType.equalsIgnoreCase("CSV")){
System.out.println("Generate CSV report");
}
if(reportType.equalsIgnoreCase("XML")){
System.out.println("Generate XML report");
}
}
}
Open-Closed Principle开放关闭原则(OCP)
Open-Closed Design Principle dictates that “ Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”. This definition was provided by Bertrand Meyer.
Open —— Open to Extension . 对扩展开放 [ It means that you should design your classes so that new functionality can be added as new requirements are generated. ]
Closed —— Closed for Modification . 对修改关闭 [ It means that once you have developed a class you should never modify it , except to correct bugs! ]
软件实体(类,模块,函数)应该是可扩展的,而不是修改 !! 请看下方的示范性错误代码!它就属于违反了开启关闭的原则!! 这是代码未改需求前的代码示范 :
// Two types
public enum ReportingType {
CSV,XML;
}
// ReportingService 类
public class ReportingService {
public void generateReportBasedOnType(ReportingType reportingType){
System.out.println("===================================");
System.out.println("Generating report based on Type");
System.out.println("===================================");
if("CSV".equalsIgnoreCase(reportingType.toString())){
generateCSVReport();
}else if("XML".equalsIgnoreCase(reportingType.toString())){
generateXMLReport();
}
}
private void generateCSVReport(){
System.out.println("Generate CSV Report");
}
private void generateXMLReport(){
System.out.println("Generate XML Report");
}
}
// A main class 主程序类
public class GenerateReportMain {
public static void main(String[] args) {
ReportingService rs=new ReportingService();
// Generate CSV file
rs.generateReportBasedOnType(ReportingType.CSV);
System.out.println();
// Generate XML file
rs.generateReportBasedOnType(ReportingType.XML);
}
}
这时,假设产品又新增了一需求,需要新增一种EXCEL类型的导出方式,那么ReportingService也会要相对应的新增对EXCEL的输出方式, 此时注意了,这里就需修改 ReportingType 和 ReportingService 的代码 [ 这是示范性违背开闭原则的代码 ]
// You need to make changes in Enum ReportingType.
public enum ReportingType {
CSV,XML,EXCEL;
}
// ReportingService 类 [ It needs to make changes in ReportingService ]
public class ReportingService {
public void generateReportBasedOnType(ReportingType reportingType){
System.out.println("===================================");
System.out.println("Generating report based on Type");
System.out.println("===================================");
if("CSV".equalsIgnoreCase(reportingType.toString())){
generateCSVReport();
}else if("XML".equalsIgnoreCase(reportingType.toString())){
generateXMLReport();
} else if("Excel".equalsIgnoreCase(reportingType.toString())){
generateExcelReport();
}
}
private void generateCSVReport(){
System.out.println("Generate CSV Report");
}
private void generateXMLReport(){
System.out.println("Generate XML Report");
}
}
// A main class 主程序类
public class GenerateReportMain {
public static void main(String[] args) {
ReportingService rs=new ReportingService();
// Generate CSV file
rs.generateReportBasedOnType(ReportingType.CSV);
System.out.println();
// Generate XML file
rs.generateReportBasedOnType(ReportingType.XML);
System.out.println();
// Generate Excel file
rs.generateReportBasedOnType(ReportingType.EXCEL);
}
}
通过以上示范,可以看出,我在ReportingType类里面修改了代码(新增了enum),并且在ReportingService这个函数功能方法中也修改了代码(新增Excel的判断), 如此一来,随着数据的日益庞大,系统复杂递增,这种质量的代码是难以维护. 或许新增的功能是需要多处修改代码,这种代码的架构是违背了开放关闭原则,我们在开发的过程中需要把分界点层层分割,细节落地到每个模块,软件设计架构的时候就该考虑到细分每个实体类、实体模块、实体函数功能让每一个block实现高度解耦. 这样的工程利于修改需求和代码重构, 从0到1的代码整体框架的高度解耦化的构建利于后期的代码修改维护或是重构. 如果不的风险是: " 前人栽树, 后人乘凉 " , 那 "后人" 就真的凉了~~~ 万一那个 “前人”&“后人”都是自己呢?
再以下这段示范下遵守开放关闭原则的设计模式:
// ReportingStrategy 接口
public interface ReportingStrategy {
void generateReport();
}
// ReportingService 类
public class ReportingService {
public void generateReportBasedOnStrategy(ReportingStrategy reportingStrategy){
System.out.println("===================================");
System.out.println("Generating report based on Strategy");
System.out.println("===================================");
reportingStrategy.generateReport();
System.out.println();
}
}
// XMLReportingStrategy 类,实现扩展接口 [ ReportingStrategy ]
public class XMLReportingStrategy implements ReportingStrategy {
@Override
public void generateReport() {
System.out.println("Generate XML Report");
}
}
// CSVReportingStrategy 类, 实现扩展接口 [ ReportingStrategy ]
public class CSVReportingStrategy implements ReportingStrategy {
@Override
public void generateReport() {
System.out.println("Generate CSV Report");
}
}
// ExcelReportingStrategy 类, 实现扩展接口 [ ReportingStrategy ]
public class ExcelReportingStrategy implements ReportingStrategy {
@Override
public void generateReport() {
System.out.println("Generate Excel Report");
}
}
// A main class 类
public class GenerateReportMain {
public static void main(String[] args) {
ReportingService rs=new ReportingService();
//Generate CSV report
ReportingStrategy csvReportingStrategy=new CSVReportingStrategy();
rs.generateReportBasedOnStrategy(csvReportingStrategy);
//Generate XML report
ReportingStrategy xmlReportingStrategy=new XMLReportingStrategy();
rs.generateReportBasedOnStrategy(xmlReportingStrategy);
//Generate Excel report
ReportingStrategy ExcelReportingStrategy=new ExcelReportingStrategy();
rs.generateReportBasedOnStrategy(ExcelReportingStrategy);
}
}
Liskov Substitution Principle里氏替换原则(LSP)
The principle was introduced by Barbara Liskov in 1987 and according to this principle “Derived or child classes must be substitutable for their base or parent classes“.
里氏替换原则, 是由一位计算机科学家Barbara Liskov 提到 “数据的抽象与层次”的概念。派生类对象可以在程式中代替基类对象, 换句话就是子类必须能够替代掉他们的父类。 在面向对象程序设计中, 度量权衡对象之间的继承关系的质量,通常继承关系必须确保超类所拥有的性质在子类中仍然成立这一特性。通常可以这么设计 :
派生类能可以实现父类的抽象方法,但不能覆盖基类的非抽象方法
// 基类 [抽象方法的基类]
public abstract class AnimalLegCount {
public abstract void countLeg(int num) ;
}
// 派生类 - DogLegCount
public class DogLegCount extends AnimalLegCount{
@Override
public void countLeg(int num) {
System.out.println("DogLegs="+num);
}
}
// 派生类 - BirdLegCount
public class BirdLegCount extends AnimalLegCount {
@Override
public void countLeg(int leg) {
System.out.println("BirdLegs="+leg);
}
}
// 派生类 - SnakeLegCount
public class SnakeLegCount extends AnimalLegCount {
@Override
public void countLeg(int leg) {
System.out.println("SnakeLegs="+leg);
}
}
// 主启动类
public class MainStarter {
public static void main(String[] args) {
BirdLegCount birdLegCount = new BirdLegCount();
birdLegCount.countLeg(2);
SnakeLegCount snakeLegCount = new SnakeLegCount();
snakeLegCount.countLeg(0);
DogLegCount dogLegCount = new DogLegCount();
dogLegCount.countLeg(4);
}
}
// output: BirdLegs=2 SnakeLegs=0 DogLegs=4
不能覆盖基类的非抽象方法, 重写了基类的方法违背了LSP原则 , 看如下代码:
// 基类 [非抽象方法的基类]
public class AnimalLegCount {
public int countLeg(int num){
return num;
}
}
// 派生类 - DogLegCount
public class DogLegCount extends AnimalLegCount{
@Override
public int countLeg(int num) {
return num + 1;
}
}
// 主启动类
public class MainStarter {
public static void main(String[] args) {
DogLegCount dogLegCount = new DogLegCount();
System.out.println( "DogLegs=" + dogLegCount.countLeg(2) );
}
}
// output: DogLegs=3
派生类可以个性化定义自有的方法
// 基类
public abstract class AnimalLegCount {
public abstract void countLeg(int num) ;
}
// 派生类
public class DogLegCount extends AnimalLegCount{
@Override
public void countLeg(int num) { // 继承基类的方法
System.out.println("DogLegs="+num);
}
public int legs(int legs ) { // 定义自有的方法
return legs;
}
}
// 主程序类
public class MainStarter {
public static void main(String[] args) {
DogLegCount dogLegCount = new DogLegCount();
dogLegCount.countLeg(4);
System.out.println( dogLegCount.legs(4) );
}
}
// output : DogLegs=4 4
Interface Segregation Principle接口隔离原则(ISP)
以下是示范未隔离接口的代码, 能看出以下代码有什么问题吗?
// Set 接口
public interface Set {
boolean add(E e);
boolean contains(Object o);
E ceiling(E e);
E floor(E e);
}
// TreeSet , 实现Set接口
public class TreeSet implements Set{
@Override
public boolean add(Object e) {
// Implement this method
return false;
}
@Override
public boolean contains(Object o) {
// Implement this method
return false;
}
@Override
public Object ceiling(Object e) {
// Implement this method
return null;
}
@Override
public Object floor(Object e) {
// Implement this method
return null;
}
}
// HashSet , 实现Set接口
public class HashSet implements Set{
@Override
public boolean add(Object e) {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Object ceiling(Object e) {
// This method is not applicable for HashSet
return null;
}
@Override
public Object floor(Object e) {
// This method is not applicable for HashSet
return null;
}
}
HashSet类的代码不需要实现 ceiling 和 floor 的函数方法 [HashSet是不保证有序] 。所以,我们接口需要重新设计!!
看下方的代码,把Set接口部分的 ceiling 和 floor 函数抽离出来定义成另一种NavigableSet接口, TreeSet 可以同时实现两种接口,而HashSet没有ceiling 和 floor 函数,只需要实现 Set接口。 这种方式从而达到接口的隔离
// 重新设计的 NavigableSet 接口
public interface NavigableSet {
E ceiling(E e);
E floor(E e);
}
// 重新设计的 Set 接口
public interface Set {
boolean add(E e);
boolean contains(Object o);
}
// TreeSet 同实实现了两种接口Set接口、和NaviagableSet接口
public class TreeSet implements Set,NaviagableSet{
@Override
public boolean add(Object e) {
// Implement this method
return false;
}
@Override
public boolean contains(Object o) {
// Implement this method
return false;
}
@Override
public Object ceiling(Object e) {
// Implement this method
return null;
}
@Override
public Object floor(Object e) {
// Implement this method
return null;
}
}
// HashSet 实现了 Set 接口
public class HashSet implements Set{
@Override
public boolean add(Object e) {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
}
Dependency Inversion Principle依赖倒置原则(DIP)
According to this, dependency inversion principle, entities should depend only on abstractions but not on concretions. According to it, the high-level module must never rely on any low-level module but should depend on abstractions.
实体依赖于抽象而非依赖具体细节, 具体细节应取决于抽象。 由此可见,高级模块决不能依赖于任何低级模块,但它可以依赖于抽象。
示范以下代码 ,以高级模块依赖一个具体细节的时候,就违背了DIP原则:
// Driver车夫的细节
public class Driver {
public void drive(Benz benz){
benz.run();
}
}
// Benz车的细节
public class Benz {
public void run(){
System.out.println("Benz is Running");
}
}
// Client高级模块
public class Client {
public static void main(String[] args) {
Driver xu = new Driver();
Benz benz = new Benz();
// xu开奔驰车
xu.drive(benz);
}
}
如果把车夫和车都抽象出来ICar和IDriver, 让高级模块依赖于抽象,细节也依赖于抽象,如下:
public interface IDriver {
public void drive(ICar car);
}
public class Driver implements IDriver{
public void drive(ICar car){
car.run();
}
}
public interface ICar {
public void run();
}
public class Tesla implements ICar{
public void run(){
System.out.println("Tesla is Running...");
}
}
public class Benz implements ICar{
public void run(){
System.out.println("Benz is Running...");
}
}
//高层模块
public class Client {
public static void main(String[] args) {
IDriver xu = new Driver();
IDriver xiaohuihui = new Driver();
ICar tesla = new Benz();
ICar benz = new Tesla();
// 小灰灰开特斯拉
xiaohuihui.drive(tesla);
// 小徐就开奔驰
xu.drive(benz);
}
}
DIP两种注入依赖的方式:
setter injection 通过接口传递依赖对象
constructor injection 构造函数传递依赖对象
// Setter injection 以注入对象方式传递依赖对象
public interface IDriver{
//注入依赖
public void setCar(ICar car);
public void drive();
}
public class Driver implements IDriver{
private ICar car;
public void setCar(ICar car){
this.car = car;
}
public void drive(){
this.car.run();
}
}
// Constructor injection 以构造函数方式传递依赖对象
public interface IDriver {
public void drive();
}
public class Driver implements IDriver{
private ICar car;
//Constructor 构造函数注入
public void Driver(ICar car){
this.car = car;
}
public void drive(ICar car){
this.car.run();
}
}