Java設計模式七大原則
懂了設計模式,你就懂了面向對象分析和設計
(OOA/D)的精要
1. 設計模式的重要性
- 軟件工程中,設計模式(design pattern)是對軟件設計中普遍存在(反覆出現)
的各種問題,所提出的解決方案。 - 實際工作經歷來說, 當一個項目開發完後,如果客戶提出增新功能,怎麼辦?
- 原來程序員離職,你接手維護該項目怎麼辦? (維護性[可讀性、規範性])
2.設計模式的目的
- 代碼重用性 (即:相同功能的代碼,不用多次編寫
- 可讀性 (即:編程規範性, 便於其他程序員的閱讀和理解)
- 可擴展性 (即:當需要增加新的功能時,非常的方便,稱爲可維護
- 可靠性 (即:當我們增加新的功能後,對原來的功能沒有影響)
- 使程序呈現高內聚,低耦合的特性
3.設計模式常用七大原則
單一職責原則
接口隔離原則
依賴倒轉(倒置)原則
里氏替換原則
開閉原則
迪米特法則
合成複用原則
3.1單一職責原則
基本介紹:
對類來說的,即一個類應該只負責一項職責。如類A負責兩個不同職責:職責1,職責2。 當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解爲 A1,A2應用實例:
方案一:
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 單一職責原則01 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托車");
vehicle.run("汽車");
vehicle.run("飛機");
}
}
//交通工具類
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"在路上跑");
}
}
分析:
- 在方式1的run方法中,違反了單一職責原則
- 解決的方案非常的簡單,根據交通工具運行方法不同,分解成不同類即可
方案二:
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 單一職責原則01 {
public static void main(String[] args) {
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飛機");
}
}
//天空工具類
class AirVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在天上飛");
}
}
//陸地工具類
class RodaVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在路上跑");
}
}
//海洋工具類
class WaterVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在水上游");
}
}
分析:
- 遵守單- -職責原則
- 但是這樣做的改動很大,即將類分解,同時修改客戶端
- 改進:直接修改Vehicle 類,改動的代碼會比較少=>方案3
方案三:
//交通工具類
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"跑");
}
public void Roadrun(String vehicle){
System.out.println(vehicle+"在路上跑");
}
public void Waterrun(String vehicle){
System.out.println(vehicle+"在水裏遊");
}
public void Airrun(String vehicle){
System.out.println(vehicle+"在天上飛");
}
}
分析:
1.這種修改方法沒有對原來的類做大的修改,只是增加方法
2. 這裏雖然沒有在類這個級別上遵守單- -職責原則,但是在方法級別上,仍然是遵守單- -職責
單一職責原則注意事項和細節小結:
1)降低類的複雜度, 一個類只負責-項職責。
2)提高類的可讀性,可維護性
3)降低變更引起的風險
4)通常情況下,我們應當遵守單一職責原則,只有邏輯足夠簡單,纔可以在代碼級違反單- -職責原則;只有類中方法數量足夠少,可以在方法級別保持單- -職責原則
3.2接口隔離原則
基本介紹:
客戶端不應該依賴它不需要的接口,即一個類對另一個類的依賴應該建立在最小的接口上圖片解釋:
類A通過接口Interface1依賴類B,類C通過接口Interface1依賴類D,如果接口Interface1對於類A和類C來說不是最小接口,那麼類B和類D必須去實現他們不需要的方法。
按隔離原則應當這樣處理:將接口Interface1拆分爲獨立的幾個接口,類A和類C分別與他們需要的接口建立依賴關係。也就是採用接口隔離原則
應用實例:
不遵守接口隔離原則
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 接口隔離原則 {
public static void main(String[] args) {
A a = new A();
a.run(new D());//此時不需要實現類D的D E方法
C c=new C();
c.run(new B());//此時不需要實現類B的B C方法
}
}
interface Interafce{
void A();
void B();
void C();
void D();
void E();
}
class D implements Interafce{
@Override
public void A() {
System.out.println("D類實現Father接口方法A");
}
@Override
public void B() {
System.out.println("D類實現Father接口方法B");
}
@Override
public void C() {
System.out.println("D類實現Father接口方法c");
}
@Override
public void D() {
System.out.println("D類實現Father接口方法D");
}
@Override
public void E() {
System.out.println("D類實現Father接口方法E");
}
}
class B implements Interafce{
@Override
public void A() {
System.out.println("c類實現Father接口方法A");
}
@Override
public void B() {
System.out.println("c類實現Father接口方法B");
}
@Override
public void C() {
System.out.println("c類實現Father接口方法c");
}
@Override
public void D() {
System.out.println("c類實現Father接口方法D");
}
@Override
public void E() {
System.out.println("c類實現Father接口方法E");
}
}
/*
* 類A的參數傳入了接口,實例化對象時,假如想使用C類的方法,將C類傳入,此時C類已經實現接口
* */
class A{
void runA(Interafce d){
d.A();
}
void runB(Interafce d){
d.B();
}
void runC(Interafce d){
d.C();
}
}
class C{
void run(Interafce d){
d.A();
}
void runD(Interafce d){
d.D();
}
void runE(Interafce d){
d.E();
}
}
不遵守接口隔離原則出現問題:
應傳統方法的問題和使用接口隔離原則改進.- 類A通過接口Interfacel 依賴類B,類C通過接口Interfacel依賴類D,如果接口Interfacel 對於類A和類C來說不是最小接口,那麼類B和類D必須去實現他們不需要的方法
- 將接口 Interface1拆分爲獨立的幾個接口,類A和類C分別與他們需要的接口建立依賴關係。也就是採用接口隔離原則
- 接口Interfacel 中出現的方法,根據實際情況拆分爲三個接口
遵守接口隔離原則代碼實現:
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 接口隔離原則 {
public static void main(String[] args) {
A a = new A();
a.runA(new D());
a.runB(new D());
a.runC(new D());
System.out.println("***************");
C c=new C();
c.runA(new B());
c.runD(new B());
c.runE(new B());
}
}
interface Interafce{
void A();
}
interface Interafce2{
void B();
void C();
}
interface Interafce3{
void D();
void E();
}
class D implements Interafce,Interafce2{
@Override
public void A() {
System.out.println("D類實現Father接口方法A");
}
@Override
public void B() {
System.out.println("D類實現Father接口方法B");
}
@Override
public void C() {
System.out.println("D類實現Father接口方法c");
}
}
class B implements Interafce,Interafce3{
@Override
public void A() {
System.out.println("c類實現Father接口方法A");
}
@Override
public void D() {
System.out.println("c類實現Father接口方法D");
}
@Override
public void E() {
System.out.println("c類實現Father接口方法E");
}
}
/*
* 類A的參數傳入了接口,實例化對象時,假如想使用C類的方法,將C類傳入,此時C類已經實現接口
* */
class A{
void runA(Interafce d){
d.A();
}
void runB(Interafce2 d){
d.B();
}
void runC(Interafce2 d){
d.C();
}
}
class C{
void runA(Interafce d){
d.A();
}
void runD(Interafce3 d){
d.D();
}
void runE(Interafce3 d){
d.E();
}
}
代碼結果:
3.3依賴倒轉原則
基本介紹:
- 高層模塊不應該依賴低層模塊,二者都應該依賴其抽象
- 抽象不應該依賴細節,細節應該依賴抽象
- 依賴倒轉(倒置)的中心思想是面向接口編程
- 依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的
多。以抽象爲基礎搭建的架構比以細節爲基礎的架構要穩定的多。在java中,抽象
指的是接口或抽象類,細節就是具體的實現類 - 使用接口或抽象類的目的是制定好規範,而不涉及任何具體的操作,把展現細節的
任務交給他們的實現類去完成
應用實例:
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 依賴倒轉原則 {
public static void main(String[] args) {
Person person = new Person();
person.Recive(new Email());
}
}
class Person {
void Recive(Email email) {
System.out.println("接收到的");
email.getInfo();
}
}
class Email {
void getInfo() {
System.out.println("信息內容: 你好我是小明!");
}
}
分析:
完成Person接收消息的功能
- 簡單,比較容易想到
- 如果我們獲取的對象是微信,短信等等,則新增類,同時Perons也要增加相應的接收方法
- 解決思路:引入一個抽象的接口IReceiver, 表示接收者,這樣Person類與接口IReceiver發生依賴因爲 Email, WeiXin等等屬於接收的範圍,他們各自實現IReceiver 接口就ok,這樣我們就符號依賴倒轉原則
遵守依賴倒轉原則代碼實現
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 依賴倒轉原則 {
public static void main(String[] args) {
Person person = new Person();
person.Recive(new Email());
person.Recive(new Weixin());
}
}
/*
* 定義接口
**/
interface IReceiver{
public void getInfo();
}
class Email implements IReceiver{
public void getInfo() {
System.out.println("郵件信息內容: 你好我是小明!");
}
}
class Weixin implements IReceiver{
public void getInfo() {
System.out.println("微信信息內容: 你好我是小紅!");
}
}
class Person {
void Recive(IReceiver iReceiver) {
System.out.println("接收到的");
iReceiver.getInfo();
}
}
依賴關係傳遞的三種方式和應用案例:
-
接口傳遞
-
構造方法傳遞
-
setter方式傳遞
接口傳遞依賴關係案例:
package DesignPattern.Test;
import com.sym.JVM.T;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 依賴關係傳遞 {
public static void main(String[] args) {
MyOpenAndClose myOpenAndClose =new MyOpenAndClose();
myOpenAndClose.open(new XiaoMi());
}
}
/*
* 開機與關機的接口
* */
interface OpenAndClose{
/*
* 開機:參數傳入一個接口 電視
* 抽象方法,接收接口
* */
public void open(TV tv);
}
/*
*電視接口
*/
interface TV{
public void play(); //表示電視機已打開
}
class XiaoMi implements TV{
@Override
public void play() {
System.out.println("小米電視已打開。。");
}
}
class MyOpenAndClose implements OpenAndClose{
@Override
public void open(TV tv) {
tv.play();
}
}
構造方法傳遞依賴關係案例:
package DesignPattern.Test;
import com.sym.JVM.T;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 依賴關係傳遞 {
public static void main(String[] args) {
MyOpenAndClose myOpenAndClose = new MyOpenAndClose(new XiaoMi());
myOpenAndClose.open();
}
}
/*
* 開機與關機的接口
* */
interface OpenAndClose {
/*
* 開機:參數傳入一個接口 電視
* 抽象方法,接收接口
* */
public void open();
}
/*
*電視接口
*/
interface TV {
public void play(); //表示電視機已打開
}
class XiaoMi implements TV {
@Override
public void play() {
System.out.println("小米電視已打開。。");
}
}
class MyOpenAndClose implements OpenAndClose {
public TV tv;
public MyOpenAndClose(TV tv) {
this.tv = tv;
}
@Override
public void open() {
this.tv.play();
}
}
setter傳遞依賴關係案例:
package DesignPattern.Test;
import com.sym.JVM.T;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 依賴關係傳遞 {
public static void main(String[] args) {
MyOpenAndClose myOpenAndClose = new MyOpenAndClose();
myOpenAndClose.setTv(new XiaoMi());
myOpenAndClose.open();
}
}
/*
* 開機與關機的接口
* */
interface OpenAndClose {
/*
* 開機:參數傳入一個接口 電視
* 抽象方法,接收接口
* */
public void open();
public void setTv(TV tv);
}
/*
*電視接口
*/
interface TV {
public void play(); //表示電視機已打開
}
class XiaoMi implements TV {
@Override
public void play() {
System.out.println("小米電視已打開。。");
}
}
class MyOpenAndClose implements OpenAndClose {
private TV tv;
@Override
public void open() {
this.tv.play();
}
@Override
public void setTv(TV tv) {
this.tv = tv;
}
}
小結:
- 低層模塊儘量都要有抽象類或接口,或者兩者都有,程序穩定性更好.
- 變量的聲明類型儘量是抽象類或接口, 這樣我們的變量引用和實際對象間,就存在
一個緩衝層,利於程序擴展和優化 - 繼承時遵循里氏替換原則
3.4里氏替換原則
oo中的繼承性的思考和說明:
- 繼承包含這樣一層含義:父類中凡是已經實現好的方法,實際上是在設定規範和契
約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類對這些已經實
現的方法任意修改,就會對整個繼承體系造成破壞。 - 繼承在給程序設計帶來便利的同時,也帶來了弊端。比如使用繼承會給程序帶來侵
入性,程序的可移植性降低,增加對象間的耦合性,如果一個類被其他的類所繼承,
則當這個類需要修改時,必須考慮到所有的子類,並且父類修改後,所有涉及到子
類的功能都有可能產生故障 - 問題提出:在編程中,如何正確的使用繼承? => 里氏替換原則
基本介紹 :
-
如果對每個類型爲T1的對象o1,都有類型爲T2的對象o2,使得以T1定義的所有程序
P在所有的對象o1都代換成o2時,程序P的行爲沒有發生變化,那麼類型T2是類型T1
的子類型。換句話說,所有引用基類的地方必須能透明地使用其子類的對象。 -
在使用繼承時,遵循里氏替換原則,在子類中儘量不要重寫父類的方法
-
里氏替換原則告訴我們,繼承實際上讓兩個類耦合性增強了,在適當的情況下,可
以通過聚合,組合,依賴 來解決問題。
應用實例:
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 里氏替換原則 {
public static void main(String[] args) {
TestA a = new TestA();
System.out.println("11-3=" + a.function1(11, 3));
System.out.println("1-8=" + a.function1(1, 8));
System.out.println("-----------------------");
TestB b = new TestB();
System.out.println("11-3=" + b.function1(11, 3));
System.out.println("1-8=" + b.function1(1, 8));
System.out.println("11+3+9=" + b.function2(11, 3));
}
}
class TestA{
public int function1(int a,int b){
return a-b;
}
}
class TestB extends TestA{
public int function1(int a,int b){
return a + b;
}
public int function2(int a,int b){
return function1(a,b)+9;
}
}
結果:
分析問題:
1) 我們發現原來運行正常的相減功能發生了錯誤。原因就是類B無意中重寫了父類的方法,造成原有功能出現錯誤。在實際編程中,我們常常會通過重寫父類的方法完 成新的功能,這樣寫起來雖然簡單,但整個繼承體系的複用性會比較差。特別是運 行多態比較頻繁的時候 2) 通用的做法是:原來的父類和子類都繼承一個更通俗的基類,原有的繼承關係去掉,採用依賴,聚合,組合等關係代替.遵守里氏替換原則的代碼:
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 里氏替換原則 {
public static void main(String[] args) {
TestA a = new TestA();
System.out.println("11-3=" + a.function1(11, 3));
System.out.println("1-8=" + a.function1(1, 8));
System.out.println("-----------------------");
TestB b = new TestB();
System.out.println("11-3=" + b.function3(11, 3));
System.out.println("1-8=" + b.function3(1, 8));
System.out.println("11+3+9=" + b.function2(11, 3));
}
}
class Base{
//把更加基礎的方法和成員寫到Base類
}
class TestA extends Base{
public int function1(int a,int b){
return a-b;
}
}
class TestB extends Base{
//函數function1重寫了TestA的方法
public int function1(int a,int b){
return a + b;
}
public int function2(int a,int b){
return function1(a,b)+9;
}
//我們仍然想要使用TestA的方法,使用組合關係
private TestA a=new TestA();
public int function3(int a,int b){
return this.a.function1(a,b);
}
}
結果:
3.5開閉原則
基本介紹:
應用實例:
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 開閉原則 {
public static void main(String[] args) {
Editor editor = new Editor();
editor.drawShape(new Rectangle());
editor.drawShape(new Circle());
}
}
//基類
class Shape{
int type;
}
//矩形
class Rectangle extends Shape{
Rectangle(){
super.type=1;//設置矩形的識別碼爲1
}
}
//圓形
class Circle extends Shape{
Circle(){
super.type=2;//設置圓形的識別碼爲2
}
}
//繪製圖形【使用方】
class Editor{
//接收Shape對象,然後根據type識別碼,繪製不同圖形
public void drawShape(Shape shape){
if (shape.type==1){
drawRectangle();
}else if (shape.type==2){
drawCircle();
}else if (shape.type==3){
}
}
private void drawRectangle() {
System.out.println(" 繪製矩形 ");
}
private void drawCircle() {
System.out.println(" 繪製圓形 ");
}
}
分析:
方式1的優缺點
- 優點是比較好理解,簡單易操作。
- 缺點是違反了設計模式的ocp原則,即對擴展開放(提供方),對修改關閉(使用方)。
即當我們給類增加新功能的時候,儘量不修改代碼,或者儘可能少修改代碼. - 比如我們這時要新增加一個圖形種類 三角形,我們需要做如下修改,修改的地方
較多
添加繪製三角形功能代碼演示:
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 開閉原則 {
public static void main(String[] args) {
Editor editor = new Editor();
editor.drawShape(new sanjiao());
}
}
//基類
class Shape{
int type;
}
//新添加三角形
class sanjiao extends Shape{
sanjiao(){
super.type=3;//設置三角形的識別碼爲3
}
}
//繪製圖形【使用方】
class Editor{
//接收Shape對象,然後根據type識別碼,繪製不同圖形
public void drawShape(Shape shape){
if (shape.type==3){
drawSanjiao();
}
}
private void drawSanjiao(){
System.out.println(" 繪製三角形 ");
}
}
遵守開閉原則:
改進的思路分析
思路:把創建Shape類做成抽象類,並提供一個抽象的draw方法,讓子類去實現即可,
這樣我們有新的圖形種類時,只需要讓新的圖形類繼承Shape,並實現draw方法即可,
使用方的代碼就不需要修 -> 滿足了開閉原則
package DesignPattern.Test;
/**
* @author 孫一鳴 on 2020/2/2
*/
public class 開閉原則 {
public static void main(String[] args) {
Editor editor = new Editor();
editor.drawShape(new Rectangle());
editor.drawShape(new Circle());
editor.drawShape(new sanjiao());
}
}
//基類
abstract class Shape{
int type;
public abstract void draw();
}
//矩形
class Rectangle extends Shape{
Rectangle(){
super.type=1;//設置矩形的識別碼爲1
}
@Override
public void draw() {
System.out.println(" 繪製矩形 ");
}
}
//圓形
class Circle extends Shape{
Circle(){
super.type=2;//設置圓形的識別碼爲2
}
@Override
public void draw() {
System.out.println(" 繪製圓形 ");
}
}
//新添加三角形
class sanjiao extends Shape{
sanjiao(){
super.type=3;//設置三角形的識別碼爲3
}
@Override
public void draw() {
System.out.println(" 繪製三角形 ");
}
}
//繪製圖形【使用方】
class Editor{
//接收Shape對象,然後根據type識別碼,繪製不同圖形
public void drawShape(Shape shape){
shape.draw();
}
}
此時我們再要添加一個新的繪製圖形時,我們需要改變的只有:
//新添加其他
class Other extends Shape{
@Override
public void draw() {
System.out.println(" 繪製其他圖形 ");
}
}
editor.drawShape(new Other());
使用方並沒有做任何代碼上的變動.滿足了OCP原則
3.6迪米特法則
基本介紹:
- 一個對象應該對其他對象保持最少的瞭解
- 類與類關係越密切,耦合度越大
- 迪米特法則(Demeter Principle)又叫最少知道原則,即一個類對自己依賴的類知道的
越少越好。也就是說,對於被依賴的類不管多麼複雜,都儘量將邏輯封裝在類的內
部。對外除了提供的public 方法,不對外泄露任何信息 - 迪米特法則還有個更簡單的定義:只與直接的朋友通信
- 直接的朋友:每個對象都會與其他對象有耦合關係,只要兩個對象之間有耦合關係,我們就說這兩個對象之間是朋友關係。耦合的方式很多,依賴,關聯,組合,聚合等。其中,我們稱出現成員變量,方法參數,方法返回值中的類爲直接的朋友,而出現在局部變量中的類不是直接的朋友。也就是說,陌生的類最好不要以局部變量的形式出現在類的內部。
應用實例:
有一個學校,下屬有各個學院和總部,現要求打印出學校總部員工ID和學院員工的id
package DesignPattern.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @author 孫一鳴 on 2020/2/3
*/
//客戶端
public class 迪米特法則 {
public static void main(String[] args) {
//創建了一個 SchoolManager 對象
SchoolManager schoolManager = new SchoolManager();
//輸出學院的員工id 和 學校總部的員工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//學校總部員工類
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//學院的員工類
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//管理學院員工的管理類
class CollegeManager {
//返回學院的所有員工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) { //這裏我們增加了10個員工到 list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("學院員工id= " + i);
list.add(emp);
}
return list;
}
}
//學校管理類
//分析 SchoolManager 類的直接朋友類有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一個陌生類,這樣違背了 迪米特法則
class SchoolManager {
//返回學校總部的員工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) { //這裏我們增加了5個員工到 list
Employee emp = new Employee();
emp.setId("學校總部員工id= " + i);
list.add(emp);
}
return list;
}
//該方法完成輸出學校總部和學院員工信息(id)
void printAllEmployee(CollegeManager sub) {
//分析問題
//1. 這裏的 CollegeEmployee 不是 SchoolManager的直接朋友
//2. CollegeEmployee 是以局部變量方式出現在 SchoolManager
//3. 違反了 迪米特法則
//獲取到學院員工
List<CollegeEmployee> list1 = sub.getAllEmployee();
System.out.println("------------學院員工------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
//獲取到學校總部員工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------學校總部員工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
案例分析:
本案例共有4個實體類,分別爲:- 學校總部員工類CollegeEmployee
- 學院的員工類CollegeEmployee
- 學院員工的管理類CollegeManager
- 學校總部員工管理類SchoolManager
出現問題:學校總部員工管理類SchoolManager 中以局部變量方式使用了 學院的員工類CollegeEmployee,違反了 迪米特法則,這裏的 CollegeEmployee 不是SchoolManager的直接朋友
案例改進:
1)前面設計的問題在於SchoolManager中,CollegeEmployee類並不是SchoolManager類的直接朋友 (分析)
2) 按照迪米特法則,應該避免類中出現這樣非直接朋友關係的耦合
3) 對代碼按照迪米特法則 進行改進
改進代碼:
package DesignPattern.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @author 孫一鳴 on 2020/2/3
*/
//客戶端
public class 迪米特法則 {
public static void main(String[] args) {
//創建了一個 SchoolManager 對象
SchoolManager schoolManager = new SchoolManager();
//輸出學院的員工id 和 學校總部的員工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//學校總部員工類
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//學院的員工類
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//管理學院員工的管理類
class CollegeManager {
//返回學院的所有員工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) { //這裏我們增加了10個員工到 list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("學院員工id= " + i);
list.add(emp);
}
return list;
}
public void printEmployee(){
//獲取到學院員工
List<CollegeEmployee> list1 = this.getAllEmployee();
System.out.println("------------學院員工------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
//學校管理類
//分析 SchoolManager 類的直接朋友類有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一個陌生類,這樣違背了 迪米特法則
class SchoolManager {
//返回學校總部的員工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) { //這裏我們增加了5個員工到 list
Employee emp = new Employee();
emp.setId("學校總部員工id= " + i);
list.add(emp);
}
return list;
}
//該方法完成輸出學校總部和學院員工信息(id)
void printAllEmployee(CollegeManager sub) {
//分析問題
//1. 這裏的 CollegeEmployee 不是 SchoolManager的直接朋友
//2. CollegeEmployee 是以局部變量方式出現在 SchoolManager
//3. 違反了 迪米特法則
//獲取到學院員工
sub.printEmployee();
//獲取到學校總部員工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------學校總部員工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
迪米特法則注意事項和細節:
- 迪米特法則的核心是降低類之間的耦合
- 但是注意:由於每個類都減少了不必要的依賴,因此迪米特法則只是要求降低
類間(對象間)耦合關係, 並不是要求完全沒有依賴關係
3.7合成複用原則
基本介紹:
原則是儘量使用合成/聚合的方式,而不是使用繼承3.8設計原則核心思想
- 找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代
碼混在一起。 - 針對接口編程,而不是針對實現編程。
- 爲了交互對象之間的鬆耦合設計而努力