設計模式之七大設計原則#
1. 單一職責原則#
1.1 代碼展示及 UML 類圖#
反面案例
package singlePrinciple.negative;
public class StudentManager {
public void getConnection(String url) {
System.out.println("與"+url+"建立連接");
}
public void searchStudent(String condition) {
System.out.println("根據"+condition+"查詢學生信息");
}
public void deleteStudent(String condition) {
System.out.println("根據"+condition+"刪除學生信息");
}
public void display() {
System.out.println("展示學生信息");
}
/**
* 1.修改連接的數據庫方式,需要修改getConnection
*
*/
}
正面案例的 UML 類圖
正面案例的 DBUtil.java
package singlePrinciple.positive;
//工具類
public class DBUtil {
public void getConnection(String url) {
System.out.println("與"+url+"建立連接");
}
}
Demo.java
package singlePrinciple.positive;
public class Demo {
public static void main(String[] args) {
DBUtil db=new DBUtil();
StudentDao dao=new StudentDao(db);
StudentManager sm=new StudentManager(dao);
db.getConnection("MySQL");
dao.searchStudent("孫悟空");
sm.display();
}
/**
* 1.DBUtil封裝了數據庫連接操作,更改數據庫伺服器僅僅只需要修改這個類或者配置文件
* 2.StudentDao封裝了對數據庫的增刪改查的操作,實現增刪改查比較方便
*
*/
}
然後 StudentDao.java
package singlePrinciple.positive;
public class StudentDao {
private DBUtil db;
public StudentDao(DBUtil db) {
super();
this.db = db;
}
public void searchStudent(String condition) {
System.out.println("根據"+condition+"查詢學生信息");
}
public void deleteStudent(String condition) {
System.out.println("根據"+condition+"刪除學生信息");
}
}
最後的 StudentManager.java
package singlePrinciple.positive;
public class StudentManager {
private StudentDao dao;
public StudentManager(StudentDao dao) {
super();
this.dao = dao;
}
public void display() {
System.out.println("展示學生信息");
}
}
1.2 單一職責原則的定義及作用(簡稱 SRP)#
定義:一個類僅有一個引起它變化的原因,即一個對象應該只包含一個職責。這個是最簡單,最容易理解卻是最不容易做到的一個設計原則,用於控制類的粒度大小。
作用:
==1. 類的複雜度降低 ==
==2. 可讀性提高 ==
==3. 可維護性提高 ==
==4. 對系統的擴展性和維護性有很大的幫助 ==
==5. 單一職責原則是實現高內聚,低耦合的指導方針 ==
2. 開閉原則(即開放封閉原則)#
2.1 代碼展示#
暫時無
2.2 定義及作用(簡稱 OCP)#
1.” 對擴展開放 “:表示實體的行為是可以擴展的。當應用的需求改變時,可以對模塊進行擴展,使其具有滿足那些改變的新行為
2.“對修改關閉”:表示對實體進行擴展時,不必改動模塊的源代碼或者二進制代碼。
作用:
==(1)有利於進行單元測試;==
==(2)可以提高復用性;==
==(3)提高可維護性;==
==(4)符合面向對象開放的要求;==
3. 接口隔離原則(簡稱 ISP)#
3.1 代碼#
反面例子,未採用接口隔離的例子。
package InterfaceIsolation.negative;
public interface HandleScore {
void update();
void insert();
void delete();
void print();
void search();
}
package InterfaceIsolation.negative;
public class Student implements HandleScore {
@Override
public void update() {
// TODO Auto-generated method stub
}
@Override
public void insert() {
// TODO Auto-generated method stub
}
@Override
public void delete() {
// TODO Auto-generated method stub
}
@Override
public void print() {
// TODO Auto-generated method stub
System.out.println("輸出學生成績!!!!!");
}
@Override
public void search() {
// TODO Auto-generated method stub
}
/*
* 存在接口污染
* 因為學生只能查詢成績,而不能修改,記錄
* 如果實現接口,除了輸出成績,其他接口用不到
*/
}
採用接口隔離原則後的案例 UML 如下:
package InterfaceIsolation.positive;
public interface Modifyable {
void delete();
void insert();
}
package InterfaceIsolation.positive;
public interface Printable {
void print();
}
package InterfaceIsolation.positive;
public interface Searchable {
void search();
}
package InterfaceIsolation.positive;
public interface Updateable {
void update();
}
package InterfaceIsolation.positive;
public class Student implements Searchable{
@Override
public void search() {
// TODO Auto-generated method stub
}
}
package InterfaceIsolation.positive;
public class Teacher implements Searchable,Updateable,Printable{
@Override
public void print() {
// TODO Auto-generated method stub
}
@Override
public void update() {
// TODO Auto-generated method stub
}
@Override
public void search() {
// TODO Auto-generated method stub
}
}
package InterfaceIsolation.positive;
public class AcademicSecretary implements Modifyable,Printable,Searchable{
@Override
public void search() {
// TODO Auto-generated method stub
}
@Override
public void print() {
// TODO Auto-generated method stub
}
@Override
public void delete() {
// TODO Auto-generated method stub
}
@Override
public void insert() {
// TODO Auto-generated method stub
}
}
3.2 定義及作用#
3.2.1 接口隔離原則:#
(1)客戶端不應該依賴它不需要的接口;
(2)類之間的依賴關係應該建立在最新的接口上;
接口隔離原則是對接口的使用進行約束的一個原則
3.2.2 接口#
(1)類接口:使用 interface 定義的接口;
(2)實例接口:使用 Class 定義的類也是一種接口;
3.2.3 隔離#
==(1)客戶端和它不需要的接口隔離,即客戶端不使用它不需要的接口,使用該接口的客戶端僅知道與之相關的方法;==
==(2)每一個接口應該承擔一種相對獨立的角色,不干不該幹的事,該幹的事都要幹,接口中沒有多餘的方法。==
3.2.4 作用#
(1)避免接口感染;
(2)提高靈活性;
(3)提供制定服務;
(4)實現高內聚。
4. 依賴倒置原則(簡稱 DIP)#
4.1 代碼#
4.1.1 不使用該原則案例如下#
package Dependency.negative;
public class AutoRead {
public void read(TextRead r) {
r.read();
}
public void read(DocRead r) {
r.read();
}
public void read(ExlRead r) {
r.read();
}
public void read(XMLRead r) {
r.read();
/**
* 修改源代碼,可能帶來很多隱患,違反了開閉原則
*/
}
}
package Dependency.negative;
public class Demo {
public static void main(String[] args) {
AutoRead ar=new AutoRead();
ar.read(new TextRead());
ar.read(new DocRead());
ar.read(new ExlRead());
}
/**
* 如果此時要增加讀取xml文件的功能,那就需要新建xlm讀取功能類,然後再AutoRead方法中增加read此功能,這樣違反了開閉原則
*
*/
}
package Dependency.negative;
public class DocRead {
public void read() {
System.out.println("讀取doc文件!!!");
}
}
package Dependency.negative;
public class ExlRead {
public void read() {
System.out.println("讀取Excel文件!!");
}
}
package Dependency.negative;
public class TextRead {
public void read() {
System.out.println("讀取text文件!");
}
}
package Dependency.negative;
public class XMLRead {
public void read() {
System.out.println("讀取xml文件");
}
}
4.1.2 使用原則案例如下:#
package Dependency.positive;
public interface Readable {
void read();
}
package Dependency.positive;
public class AutoRead{
public void read(Readable r) {
r.read();
}
}
package Dependency.positive;
public class Demo {
public static void main(String[] args) {
AutoRead ar=new AutoRead();
ar.read(new DocRead());
ar.read(new ExlRead());
ar.read(new TxtRead());
ar.read(new XMLRead());
}
}
package Dependency.positive;
public class DocRead implements Readable{
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("讀取doc文件");
}
}
package Dependency.positive;
public class ExlRead implements Readable{
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("讀取xls文件。");
}
}
package Dependency.positive;
public class TxtRead implements Readable{
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("讀取txt文件。");
}
}
package Dependency.positive;
public class XMLRead implements Readable{
@Override
public void read() {
// TODO Auto-generated method stub
System.out.println("讀取xml文件");
}
}
4.2 定義及作用#
4.2.1 定義:#
高層模塊不應該依賴低層模塊,他們都應該依賴抽象,抽象不應該依賴細節,細節應該依賴於抽象
本質就是通過抽象(接口或者抽象類)使各個類或模塊的實現彼此獨立,不互相影響,實現模塊機間的鬆耦合。
4.2.2 作用:#
==(1)減少類間的耦合性;==
==(2)提高系統的穩定,降低並發開放引起的風險;==
==(3)提高代碼的可讀性和可維護性;==
5. 里氏替換原則(簡稱 LSP)#
5.1 代碼#
5.1.1 反例代碼:#
package substitution.negative;
public class ReadDoc extends ReadFile{
@Override
public void read(String fileName) {
// TODO Auto-generated method stub
System.out.println("讀取doc文件:"+fileName);
}
}
package substitution.negative;
public class ReadFile {
public void read(String fileName) {
System.out.println("讀取excel文件"+fileName);
}
}
package substitution.negative;
public class Demo {
public static void readFile(ReadFile rf,String fileName) {
rf.read(fileName);
}
public static void main(String[] args) {
readFile(new ReadFile(),"a.xsl");
readFile(new ReadDoc(),"b.xsl");
}
}
5.1.2 正面案例:#
UML 如下:
package substitution.positive;
public interface Readable {
void read(String fileName);
}
package substitution.positive;
public class ReadDoc implements Readable{
@Override
public void read(String fileName) {
// TODO Auto-generated method stub
System.out.println("讀取doc文件:"+fileName);
}
}
package substitution.positive;
public class ReadXsl implements Readable{
@Override
public void read(String fileName) {
// TODO Auto-generated method stub
System.out.println("讀取Excel文件:"+fileName);
}
}
package substitution.positive;
/**
*
* @author ASUS
*繼承有優點也有缺點
*繼承是侵入性的,增加了耦合
*/
public class Demo {
public static void readFile(Readable rf,String fileName) {
rf.read(fileName);
}
public static void main(String[] args) {
Readable r=new ReadDoc();
readFile(r,"a.doc");
ReadXsl rs=new ReadXsl();
readFile(rs,"b.xsl");
}
}
5.2 定義及作用#
5.2.1 定義#
所有引用基類的地方必須能透明地使用其子類對象,即替換成子類之後,系統的行為不會發生變化。
里氏替換原則定義了繼承關係要遵守的 4 個規則:
==(1) 子類可以實現父類的抽象方法,但不能覆寫父類的非抽象方法;==
==(2) 子類中可以增加自己特有的方法,體現子類的個性;==
==(3) 當子類的方法實現父類的方法時 (重寫 / 重載或實現抽象方法) 時,方法的前置條件 (即方法的輸入參數) 要比父類方法的輸入參數更寬鬆或相等;==
==(4) 當子類的方法實現父類的方法時 (重寫 / 重載或實現抽象方法),方法的後置條件 (即方法的輸出 / 返回值) 要比父類更嚴格或相等。==
5.2.2 作用#
(1)約束繼承泛濫,開閉原則的一種體現;
(2)加強程序的健壯性,在程序變化可以做到非常好的兼容性,提高程序的維護性,擴展性;
(3)降低需求變更引入的風險。
6. 組合復用原則(簡稱 CRP)#
6.1 代碼#
反面:
package combined.negative;
class Person{
}
class Teacher extends Person{
}
class Student extends Person{
}
class PrimarySchoolTeacher extends Teacher{
}
class JuniorSchoolTeacher extends Teacher{
}
class HighSchoolTeacher extends Teacher{
}
class PrimaryStudent extends Student{
public void say() {
System.out.println("我是小學生");
}
}
class JuniorStudent extends Student{
public void say() {
System.out.println("我是初中生");
}
}
class HighStudent extends Student{
public void say() {
System.out.println("我是高中生");
}
}
class Logistics extends Person{
}
class PrimaryLigistics extends Logistics{
}
class JuniorLigistics extends Logistics{
}
class HighLigistics extends Logistics{
}
public class Demo {
public static void main(String[] args) {
}
}
正面:
package combined.positive;
abstract class Person{
private String name;
private SchoolLevel level;
public Person(String name, SchoolLevel level) {
super();
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return this.level.getLevel();
}
public void setLevel(SchoolLevel level) {
this.level = level;
}
}
class Teacher extends Person{
public Teacher(String name, SchoolLevel level) {
super(name, level);
// TODO Auto-generated constructor stub
}
public void say() {
System.out.println(super.getName()+"是"+super.getLevel()+"老師");
}
}
class Student extends Person{
public Student(String name, SchoolLevel level) {
super(name, level);
// TODO Auto-generated constructor stub
}
public void say() {
System.out.println(super.getName()+"是"+super.getLevel()+"學生");
}
}
interface SchoolLevel{
String getLevel();
}
class PrimaryLevel implements SchoolLevel{
@Override
public String getLevel() {
// TODO Auto-generated method stub
return "小學";
}
}
class JuniorLevel implements SchoolLevel{
@Override
public String getLevel() {
// TODO Auto-generated method stub
return "初中";
}
}
class HighLevel implements SchoolLevel{
@Override
public String getLevel() {
// TODO Auto-generated method stub
return "高中";
}
}
class Logistics extends Person{
public Logistics(String name, SchoolLevel level) {
super(name, level);
// TODO Auto-generated constructor stub
}
public void say() {
System.out.println(super.getName()+"是"+super.getLevel()+"後勤管理人員");
}
}
public class Demo {
public static void main(String[] args) {
SchoolLevel level=new HighLevel();
Student swk=new Student("孫悟空",level);
swk.say();
Teacher puti=new Teacher("扶梯老祖",new PrimaryLevel());
puti.say();
Logistics cattle=new Logistics("牛魔王",new JuniorLevel());
cattle.say();
}
}
6.2 定義及作用#
6.2.1 定義#
優先使用對象組合(聚合),而不是繼承來達到復用目的。
在一個新的對象裡通過關聯關係(包括組合和聚合關係) 來使用一些已有的對象,使之成為新對象的一部分,新對象通過委派調用已有對象的方法達到復用功能的目的。
即儘量使用組合(contains-a) 和聚合 (has-a), 而不是繼承(is-a)達到復用的目的
6.2.2 作用#
(1)實現簡單。通過繼承,子類擁有父類的功能;
(2)易於擴展。子類通過增加新的屬性和方法對父類進行擴展。
組合復用原則的作用
(1) 新對象存取成員對象的唯一途徑是通過成員對象的接口,這種復用是黑箱復用,使系統更加靈活,降低類與類之間的耦合度;
(2) 成員對象的內部細節對新對象是不可見的,這種復用支持封裝,減少依賴,一個類的變化對其他類造成的影響相對較少;
(3) 每一個新的類可以將焦點集中在一個任務上;
(4) 這種復用可以在運行時動態進行,新對象可動態引用與成員對象類型相同的對象。
不足之處:== 這種方式建造的系統會有較多的對象需要管理。==
7. 迪米特法則(LOD)#
不使用迪米特法則的例子
package lkp.negative;
public class ExcelToDB {
private String fileName;
private String db;
public ExcelToDB(String fileName, String db) {
super();
this.fileName = fileName;
this.db = db;
}
public String read() {
return this.fileName+"文件內容!";
}
public void connectionDB() {
System.out.println("連接"+this.db+"數據庫系統");
}
public void save(String conten) {
System.out.println("把"+this.read()+"保存在數據庫中!!");
}
}
客戶端代碼(客戶端暴露的代碼太多)
package lkp.negative;
//迪米特法則的反例
public class Client {
public static void main(String[] args) {
ExcelToDB todb=new ExcelToDB("a.xsl","MySQL");
String str=todb.read();
todb.connectionDB();
todb.save(str);
}
}
迪米特法則的優點,== 客戶端不需要知道如何轉換,僅僅調用工具類的轉換方法就能完成信息轉換過程,客戶端用最少知識完成了任務。==
迪米特法則:一個對象應當對其他的對象儘可能少的了解,不和陌生人說話,最少知識原則(==LKP==)。
儘可能減少對象之間的交互,如果一個對象需要調用另一個對象的方法,可以通過第三者調用。
朋友圈的成員:
==1. 當前對象本身(this)==
==2. 當前對象方法中的參數 ==
==3. 當前對象的實例變量之間引用的對象 ==
==4. 當前對象的實例變量如果是一個聚集,那麼聚集的元素也是朋友 ==
==5. 當前對象方法所創建的對象 ==
四個要求:
== 優先考慮將一個類設置成不變類 ==
== 儘量降低一個類的訪問權限 ==
== 謹慎使用 Serializable==
== 儘量降低成員的訪問權限 ==
package lkp.positive;
public class ExcelToDB {
private String fileName;
private String db;
public ExcelToDB(String fileName, String db) {
super();
this.fileName = fileName;
this.db = db;
}
//私有化方法,避免暴露
private String read() {
return this.fileName+"文件內容!";
}
private void connectionDB() {
System.out.println("連接"+this.db+"數據庫系統");
}
private void save(String conten) {
System.out.println("把"+this.read()+"保存在數據庫中!!");
}
public void perform() {
String str=this.read();
this.connectionDB();
this.save(str);
}
}
中介者如下
package lkp.positive;
public class UtilConvert {
private ExcelToDB todb;
public UtilConvert(ExcelToDB todb) {
super();
this.todb = todb;
}
public void convert() {
todb.perform();
}
}
客戶端
package lkp.positive;
public class Client {
public static void main(String[] args) {
ExcelToDB todb=new ExcelToDB("a.xsl","MYSQL");
UtilConvert convert=new UtilConvert(todb);
convert.convert();
}
}