設計模式-原則
阿新 • • 發佈:2020-12-20
七大設計原則
所有原則都為了降低類之間的耦合。
一、單一職責
- 降低類複雜度,一個類負責一項職責。
- 提高類可讀性,可維護性。
- 降低變更引起的風險。
- 通常情況下,應遵循。只有邏輯夠簡單,才可在程式碼級別(通過if...else...)違反;只有類方法數量足夠少,才可在方法級別(定義不同方法實現不同操作)保持單一職責
二、介面隔離
客戶端不應依賴不需要的介面,即對一個類的依賴,應該建立在最小介面上。
如:介面Interface有1、2、3等方法,B類實現了Interface介面。A類通過介面依賴B類,但A只需要用到介面中1、2兩個方法。 按介面隔離原則應把介面分離: 1、Interface分成兩個介面:Interface1有方法1、2;Interface2有方法3; 2、B類實現介面Interface1即可; 3、如果後續某個類需要用到Interface中1、2、3方法,再讓B實現Interface2。
三、依賴倒轉
- 底層模組儘量使用抽象類或介面,或兩者都有,程式穩定性更好
- 變數宣告型別儘量用抽象類或介面。這樣變數引用和實際物件存在一個緩衝層(即介面),利用程式擴充套件和優化。
- 繼承時遵循里氏替換原則
//客戶使用端 public class Client { public static void main(String[] args){ OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv(); openAndCloseTv.open(new Changhong()); openAndCloseTv.open(new Kangjia()); } } //開關介面 interface IOpenAndCloseTv{ public void open(ITV tv); } //實現 class OpenAndCloseTv implements IOpenAndCloseTv{ @Override public void open(ITV tv) { tv.play(); } } //tv型別介面 interface ITV{ public void play(); } //定義不同tv型別 class Changhong implements ITV{ @Override public void play() { System.out.println("長虹電視開啟。。。"); } } class Kangjia implements ITV{ @Override public void play() { System.out.println("康佳電視打開了。。。"); } } 結果: 長虹電視開啟。。。 康佳電視打開了。。。
依賴關係傳遞三種方式:
- 介面傳遞
//開關介面 interface IOpenAndCloseTv{ public void open(ITV tv); } //實現 class OpenAndCloseTv implements IOpenAndCloseTv{ @Override public void open(ITV tv) { tv.play(); } } ---------------------使用 public static void main(String[] args){ OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv(); openAndCloseTv.open(new Changhong()); openAndCloseTv.open(new Kangjia()); }
- 構造方法傳遞
//開關介面
interface IOpenAndCloseTv{
public void open();
}
//實現
class OpenAndCloseTv implements IOpenAndCloseTv{
private ITV tv; // 成員變數
public OpenAndCloseTv(ITV tv){
this.tv = tv;
}
@Override
public void open() {
tv.play();
}
}
---------------------使用
public static void main(String[] args){
OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv(new Changhong());
openAndCloseTv.open();
OpenAndCloseTv openAndCloseTv2 = new OpenAndCloseTv(new Kangjia());
openAndCloseTv2.open();
}
- setter方法傳遞
//開關介面
interface IOpenAndCloseTv{
public void open();
public void setTv(ITV tv)
}
//實現
class OpenAndCloseTv implements IOpenAndCloseTv{
private ITV tv; // 成員變數
@Override
public void setTv(ITV tv){
this.tv = tv;
}
@Override
public void open() {
tv.play();
}
}
---------------------使用
public static void main(String[] args){
OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
openAndCloseTv.setTv(new Changhong();
openAndCloseTv.open();
}
四、里氏替換
一個類被其他類所繼承,該類修改時,必須考慮所有子類。父類修改後,所有子類有可能產生故障。
如何正確使用繼承?--》里氏替換原則
- 使用基類的地方必須能透明使用子類物件。
- 使用繼承時,在子類中儘量不要重寫父類方法。
- 繼承實際讓兩個類耦合性增強,在適當情況下可通過聚合,組合,依賴解決問題,不要通過繼承。
如某類子類需要改寫父類方法,應把子類提升和父類一樣的檔次,把共同方法抽取,讓子類和父類繼承同一個更基層的基類。
若要改變父,就不要做父子關係了,做兄弟關係吧。
public class Liskov {
public static void main(String[] args){
A a = new A();
System.out.println("11-3="+a.func1(11,3));
System.out.println("1-8="+a.func1(1,8));
System.out.println("----------------------------");
B b = new B();
System.out.println("11-3="+b.func1(11,3)); // 這裡其實已經覆寫父類方法了,所以-的話是錯誤的。
System.out.println("1-8="+b.func1(1,8));
System.out.println("11+3+9="+b.func2(11,3));
}
}
class A {
public int func1(int num1,int num2){
return num1 - num2;
}
}
class B extends A{
// 重新了A 類方法,可能是無意識
@Override
public int func1(int a,int b){
return a + b;
}
public int func2(int a,int b){
return func1(a,b)+9;
}
}
通過里氏替換原則:
public class Liskov2 {
public static void main(String[] args){
A a = new A();
System.out.println("11-3="+ a.func1(11,3));
System.out.println("1-8="+ a.func1(1,8));
System.out.println("----------------------------");
B b = new B();
System.out.println("11+3="+ b.func1(11,3)); // 這裡其實已經覆寫父類方法了,所以-的話是錯誤的。
System.out.println("1+8="+ b.func1(1,8));
System.out.println("11+3+9="+ b.func2(11,3));
System.out.println("11-3="+ b.func3(11,3)); //實際呼叫的是A類的方法。
}
}
class base{
//共有的更基礎的方法、成員變數定義到該類
//如:
public int funcMult(int num1,int num2){
return num1*num2;
}
}
class A extends base{
public int func1(int num1,int num2){
return num1 - num2;
}
}
class B extends base{
private A aobj = new A(); //通過組合方式解決繼承問題-step1
public int func1(int a,int b){
return a + b;
}
public int func2(int a,int b){
return func1(a,b)+9;
}
public int func3(int a,int b){
return aobj.func1(a,b); //通過組合方式解決繼承問題-step2
}
}
----------結果
11-3=8
1-8=-7
----------------------------
11+3=14
1+8=9
11+3+9=23
11-3=8
五、開閉原則
- 最基礎、最重要的設計原則
- 一個實體如類、模組、函式應【擴充套件開發(對提供方),修改關閉(對使用方)】。抽象構建框架,實現擴充套件細節。
- 軟體需變化,儘量通過擴充套件程式碼而不是修改已有程式碼。
- 程式設計中遵循其他原則,及設計模式目的就是遵循開閉原則(核心)。
不遵循OCP原則:
public class Ocp {
public static void main(String[] args){
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
//接收Shape物件,呼叫draw方法
public void drawShape(Shape s) {
if (s.m_type == 1){
drawRectangle();
}else if (s.m_type == 2){
drawCircle();
}else if (s.m_type == 3){ //新增圖形,使用方需要進行修改
drawTriangle();
}
}
public void drawRectangle(){ System.out.println(" 繪製矩形 "); }
public void drawCircle() { System.out.println(" 繪製圓形 "); }
//新增圖形,使用方需要進行修改
public void drawTriangle() { System.out.println(" 繪製三角形 "); }
}
//定義各種圖形
//Shape類,基類,
abstract class Shape {
int m_type;
}
//矩形
class Rectangle extends Shape {
Rectangle() { super.m_type = 1; }
}
//圓形
class Circle extends Shape {
Circle() { super.m_type = 2; }
}
//新增三角形
class Triangle extends Shape {
Triangle() { super.m_type = 3; }
}
----------結果
繪製矩形
繪製圓形
繪製三角形
遵循OCP原則:
public class Ocp {
public static void main(String[] args) {
//使用看看存在的問題
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
graphicEditor.drawShape(new OtherGraphic());
}
}
//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
//接收Shape物件,呼叫draw方法
public void drawShape(Shape s) { s.draw(); //只需要新增圖形就可以,不需要修改程式碼。 }
}
//Shape類,遵守ocp原則通過建立一個基類,
abstract class Shape {
public abstract void draw();//抽象方法
}
//繪製不同影象時直接擴充套件,而不是修改。
//矩形
class Rectangle extends Shape {
@Override
public void draw() {System.out.println(" 繪製矩形 "); }
}
//圓形
class Circle extends Shape {
@Override
public void draw() { System.out.println(" 繪製圓形 "); }
}
//新增畫三角形
class Triangle extends Shape {
@Override
public void draw() { System.out.println(" 繪製三角形 "); }
}
//新增一個圖形
class OtherGraphic extends Shape {
@Override
public void draw() { System.out.println(" 繪製其它圖形 "); }
}
----------結果
繪製矩形
繪製圓形
繪製三角形
繪製其它圖形
六、迪米特法則
核心是為了降低類之間的耦合。
- 一個物件對其他物件保持最少了解。
- 也叫【最少知道原則】,即一個被依賴的類不管多複雜,儘量將邏輯封裝類內部,對外只提供public方法,不對外洩露任何資訊。
- 更簡單定義:只與【直接朋友(兩物件耦合即朋友,出現在成員變數,方法引數,方法返回值中的類為直接的朋友,出現在區域性變數中的類不是直接的朋友,稱陌生類)】通訊。
- 耦合方式:依賴、關聯、組合、聚合等。
- 陌生類最好不要以區域性變量出現在類內部。
非迪米特法則:
//客戶端
public class Demeter1 {
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());
}
}
}
-----------結果
------------學院員工------------
學院員工id= 0
學院員工id= 1
學院員工id= 2
學院員工id= 3
學院員工id= 4
學院員工id= 5
學院員工id= 6
學院員工id= 7
學院員工id= 8
學院員工id= 9
------------學校總部員工------------
學校總部員工id= 0
學校總部員工id= 1
學校總部員工id= 2
學校總部員工id= 3
學校總部員工id= 4
遵循迪米特法則:
//客戶端
public class Demeter1 {
public static void main(String[] args) {
System.out.println("~~~使用迪米特法則的改進~~~");
//建立了一個 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 = 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. 將輸出學院的員工方法,封裝到CollegeManager
sub.printEmployee();
//獲取到學校總部員工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------學校總部員工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
----------結果
~~~使用迪米特法則的改進~~~
------------學院員工------------
學院員工id= 0
學院員工id= 1
學院員工id= 2
學院員工id= 3
學院員工id= 4
學院員工id= 5
學院員工id= 6
學院員工id= 7
學院員工id= 8
學院員工id= 9
------------學校總部員工------------
學校總部員工id= 0
學校總部員工id= 1
學校總部員工id= 2
學校總部員工id= 3
學校總部員工id= 4
七、合成複用原則
- 儘量使用合成/聚合的方式,不是使用繼承。
依賴:通過方法引數依賴進來。
聚合:新增類屬性,通過方法設定進來。
組合:構建屬性直接new一個出來。
設計原則核心思想
- 找出可能需變化之處獨立出來,不要和不需變化的程式碼混寫。
- 針對介面程式設計,不是具體實現類程式設計
- 為互動物件之間低耦合設計努力