設(shè)計模式七大原則
設(shè)計模式體現(xiàn)了代碼的耦合性, 內(nèi)聚性以及可維護性,可擴展性,重用性,靈活性。
- 1、代碼重用性(即:相同功能的代碼,不用多次編寫)
- 2、可讀性(即:編程規(guī)范性,便于其他程序員的閱讀和理解)
- 3、可擴展性(即:當需要增加新的功能時,非常的方便,稱為可維護)
- 4、可靠性(即:當我們增加新的功能后,對原來的功能沒有影響)
- 5、使程序呈現(xiàn)高內(nèi)聚,低耦合的特性
一、單一職責原則(Single responsibility)
單一職責原則注意事項和細節(jié):
- 1、降低類的復雜度,一個類只負責一項職責;
- 2、提高類的可讀性,可維護性;
- 3、降低變更引起的風險;
- 4、通常情況下,應(yīng)當遵守單一職責原則, 只有邏輯足夠簡單,才可以在方法級違反單一職責原則。
/**
* @author Yu
* 只有類中方法數(shù)量足夠少,可以在方法級別保持單一職責原則
*/
public class SingleResponsility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("布加迪威龍");
vehicle.fly("波音747");
}
}
// 邏輯簡單,方法級別實現(xiàn)單一職責
// 邏輯復雜,分類實現(xiàn)單一職責
class Vehicle {
public void run(String string) {
System.out.println(string + ":是陸地交通工具");
}
public void fly(String string) {
System.out.println(string + ":是空中交通工具");
}
}
二、接口隔離原則(Interface Segregation)
- 1、類A通過接口 Interface1、2 依賴類B,類C通過接口 Interface1、3 依賴類D,如果接口 Interface 對于 類A 和 類C 來說不是最小接口,那么 類B 和 類D 必須去實現(xiàn)他們不需要的方法。
- 2、將接口 Interface 拆分為獨立的幾個接口,類A 和 類C 分別與他們需要的接口建立依賴關(guān)系。也就是采用接口隔離原則。
- 3、接口 Interface 中出現(xiàn)的方法,根據(jù)實際情祝拆分為三個接口。
image
public class InterfaceSegregation {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend4(new D());
c.depend5(new D());
}
}
interface interface1 {
void Operation1();
}
interface interface2 {
void Operation2();
void Operation3();
}
interface interface3 {
void Operation4();
void Operation5();
}
class B implements interface1, interface2 {
@Override
public void Operation1() {
System.out.println("B 實現(xiàn)了 Operation1");
}
@Override
public void Operation2() {
System.out.println("B 實現(xiàn)了 Operation2");
}
@Override
public void Operation3() {
System.out.println("B 實現(xiàn)了 Operation3");
}
}
class D implements interface1, interface3 {
@Override
public void Operation1() {
System.out.println("D 實現(xiàn)了 Operation1");
}
@Override
public void Operation4() {
System.out.println("D 實現(xiàn)了 Operation4");
}
@Override
public void Operation5() {
System.out.println("D 實現(xiàn)了 Operation5");
}
}
class A {
public void depend1(interface1 i) {
i.Operation1();
}
public void depend2(interface2 i) {
i.Operation2();
}
public void depend3(interface2 i) {
i.Operation3();
}
}
class C {
public void depend1(interface1 i) {
i.Operation1();
}
public void depend4(interface3 i) {
i.Operation4();
}
public void depend5(interface3 i) {
i.Operation5();
}
}
三、依賴倒轉(zhuǎn)原則(Dependence Inversion)
- 1、高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象(緩沖層);
- 2、抽象不應(yīng)該依賴細節(jié),細節(jié)應(yīng)該依賴抽象;
- 3、依賴倒轉(zhuǎn)(倒置)的中心思想是面向接口編程;
- 4、依賴倒轉(zhuǎn)原則是基于這樣的設(shè)計理念:相對于細節(jié)的多變性,抽象的東西要穩(wěn)定的多。以抽象為基礎(chǔ)搭建的架構(gòu)比以細節(jié)為基礎(chǔ)的架構(gòu)要穩(wěn)定的多。在java中, 抽象指的是接口或抽象類,細節(jié)就是具體的實現(xiàn)類;
- 5、使用接口或抽象類的目的是制定好規(guī)范,而不涉及任何具體的操作,把展現(xiàn)細節(jié)的任務(wù)交給他們的實現(xiàn)類去完成。
依賴關(guān)系三種傳遞方式:
- 接口傳遞(依賴)
- 構(gòu)造方法傳遞(依賴)
- setter方式傳遞(聚合)
public class DependenceInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive(new WeChat());
}
}
interface Info{
String getInfo();
}
class Email implements Info{
@Override
public String getInfo() {
return "Receive Email";
}
}
class WeChat implements Info{
@Override
public String getInfo() {
return "Receive WeChat";
}
}
//person 接受信息
class Person {
public void receive(Info info) {
System.out.println(info.getInfo());
}
}
四、里氏替換原則(Liskov Substitution)
- 1、里氏替換原則(Liskov Substitution Principle)在1988年,由麻省理工學院一位姓里的女士提出;
- 2、如果對每個類型為T1的對象o1,都有類型為T2的對象o2,使得以T1定義的所有程序P在所有的對象o1都代換成o2時,程序P的行為沒有發(fā)生變化,那么類型T2是類型T1的子類型。換句話說,所有引用基類的地方必須能透明地使用其子類的對象;
- 3、在使用繼承時,遵循里氏替換原則,在子類中盡量不要重寫父類的方法;
- 4、繼承實際上讓兩個類耦合性增強了,給程序帶來侵入性。在適當?shù)那闆r下,可以通過聚合,組合,依賴來解決問題;
- 5、繼承包含這樣一層含義:父類中凡是已經(jīng)實現(xiàn)好的方法,實際上是在設(shè)定規(guī)范和契約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類對這些已經(jīng)實現(xiàn)的方法任意修改,就會對整個繼承體系造成破壞。
public class LiskovSubstitution {
public static void main(String[] args) {
A a = new A();
System.out.println("2-1=" + a.func1(2, 1));
B b = new B();
System.out.println("2+1=" + b.func1(2, 1));
System.out.println("2+1+9=" + b.func2(2, 1));
System.out.println("B類使用A類方法:2-1=" + b.func3(2, 1));
}
}
class Base {
//把基礎(chǔ)方法和成員抽取成基類
public int func1(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 {
// TODO 類 B `無意` 重寫了父類 A 方法,造成原有方法發(fā)生改變。
// @Override
// public int func1(int num1, int num2) {
// return num1 + num2;
// }
@Override
public int func1(int num1, int num2) {
return num1 + num2;
}
public int func2(int num1, int num2) {
return func1(num1, num2) + 9;
}
private A a = new A();//組合
//使用 A 方法
public int func3(int num1, int num2) {
return this.a.func1(num1, num2);
}
}
五、開閉原則 OCP(Open Closed)
- 1、開閉原則(Open Closed Principle) 是編程中最基礎(chǔ)、最重要的設(shè)計原則;
- 2、一個軟件實體,比如類,模塊和函數(shù)應(yīng)該對提供方擴展開放,對使用方修改關(guān)閉。用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié);
- 3、當軟件需要變化時,盡量通過擴展軟件實體的行為來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)變化;
- 4、編程中遵循其它原則,以及使用設(shè)計模式的目的就是遵循開閉原則。
public class OpenClosed {
public static void main(String[] args) {
Use use = new Use();
use.drawShape(new Triangle());
use.drawShape(new Circle());
use.drawShape(new OtherGraphics());//只需要讓 此類繼承 抽象類,子類實現(xiàn)具體方法 OCP原則
}
}
class Use {
public void drawShape(Shape shape) {
shape.draw();
}
}
abstract class Shape {
public abstract void draw();
}
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("子類實現(xiàn)具體功能:三角形");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("子類實現(xiàn)具體功能:圓形");
}
}
class OtherGraphics extends Shape {
@Override
public void draw() {
System.out.println("子類實現(xiàn)具體功能:任何形狀");
}
}
六、迪米特法則(Demeter)
- 1、一個對象應(yīng)該對其他對象保持最少的了解(最少知道原則 LKP)。
- 2、類與類關(guān)系越密切,耦合度越大。要求降低類之間耦合,而不是完全解耦。
- 3、迪米特法則(Demeter Principle),即一個類對自己依賴的類知道的越少越好。也就是說,對于被依賴的類不管多么復雜,都盡量將邏輯封裝在類的內(nèi)部。對外除了提供public方法,不對外泄露任何信息。
- 4、迪米特法則更簡單的定義:只與直接的朋友通信。
- 5、直接的朋友:每個對象都會與其他對象有耦合關(guān)系,只要兩個對象之間有耦合關(guān)系,我們就說這兩個對象之間是朋友關(guān)系。耦合的方式很多,依賴,關(guān)聯(lián),組合,聚合 等。其中,我們稱出現(xiàn)成員變量,方法參數(shù),方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中的類不是直接的朋友。也就是說,陌生的類最好不要以局部變量的形式出現(xiàn)在類的內(nèi)部。
class A{
B b;//全局變量 - 直接朋友
public B m1(){} //方法返回值 - 直接朋友
public void m2(B b){}//方法入?yún)?- 直接朋友
public void m3(){
B b1 = new B();// 局部變量 非直接朋友
}
}
public class Demeter {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
//學院員工類
class CollegeEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//管理學院員工的管理類:
class CollegeManager {
//返回學院的所有員工 //TODO CollegeEmployee 直接朋友
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 printCollegeEmployee() {
List<CollegeEmployee> list1 = this.getAllEmployee();
System.out.println("---學院員工----");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
//學??偛繂T工類
class SchoolEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//學校管理類
//TODO 直接朋友 Employee CollegeManager
class SchoolManager {
//返回學校總部的員工
public List<SchoolEmployee> getAllEmployee() {
List<SchoolEmployee> list = new ArrayList<SchoolEmployee>();
for (int i = 0; i < 5; i++) { //這里我們增加了5個員工到list
SchoolEmployee emp = new SchoolEmployee();
emp.setId("學??偛繂T工id= " + i);
list.add(emp);
}
return list;
}
//該方法完成輸出學??偛亢蛯W院員工信息(id)
void printAllEmployee(CollegeManager sub) {
//獲取到學院員工
//TODO 非直接朋友 CollegeEmployee 應(yīng)該提取到 CollegeManager
// List<CollegeEmployee> list1 = sub.getAllEmployee();
// System.out.println("---學院員工----");
// for (CollegeEmployee e : list1) {
// System.out.println(e.getId());
// }
sub.printCollegeEmployee();//只提供方法,不把具體實現(xiàn)放在其他類里面。
//獲取到學??偛繂T工
List<SchoolEmployee> list2 = this.getAllEmployee();
System.out.println("------學??偛繂T工------");
for (SchoolEmployee e : list2) {
System.out.println(e.getId());
}
}
}
七、合成復用原則(Composite Reuse)
合成復用原則 盡量使用組合/聚合的方式,而不是使用繼承。
- 1、找出應(yīng)用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
- 2、針對接口編程,而不是針對實現(xiàn)編程。
- 3、為了交互對象之間的松耦合設(shè)計而努力。
public class CompositeReuse {
public static void main(String[] args) {
System.out.println("------依賴------");
B b = new B();
b.Operation1(new A());
System.out.println("------聚合------");
b.setA(new A());
b.Operation2();
System.out.println("------組合------");
b.Operation3();
}
}
class A {
void Operation1() {
System.out.println("A Operation1");
}
void Operation2() {
System.out.println("A Operation2");
}
void Operation3() {
System.out.println("A Operation3");
}
}
//如果只是需要用到 A類的方法,盡量不要使用繼承。而是使用,依賴,聚合,組合的方式
class B {
void Operation1(A a) {//TODO 依賴
a.Operation1();
a.Operation2();
a.Operation3();
}
//==============================================================
A a;
public void setA(A a) {
this.a = a;
}
void Operation2() {//TODO 聚合
a.Operation1();
a.Operation2();
a.Operation3();
}
//==============================================================
A a1 = new A();
void Operation3() {//TODO 組合
a1.Operation1();
a1.Operation2();
a1.Operation3();
}
}
八: UML(Unified Modeling Language)
IDEA PlantUML表示類與類之間的關(guān)系的符號
@startuml
Class1 <|-- ClassA:泛化
Class2 <-- ClassB:關(guān)聯(lián)
Class3 *-- ClassC:組合
Class4 o-- ClassD:聚合
Class5 <|.. ClassE:實現(xiàn)
Class6 <.. ClassF:依賴
@enduml
image
8.1: 依賴(Dependence)
只要是在類中用到了對方,那么他們之間就存在依賴關(guān)系。如果沒有對方,連編繹都通過不了。
- 類中用到了對方;
- 類的成員屬性;
- 方法的返回類型;
- 方法接收的參數(shù)類型;
- 方法中使用到。
image
/**
* @author Yu
* 類中用到了對方;
* 類的成員屬性;
* 方法的返回類型;
* 方法接收的參數(shù)類型;
* 方法中使用到;
*/
public class Dependence {
A a;//TODO 類的成員屬性
public A save(B b) {//TODO 方法接收的參數(shù)類型
//TODO 方法的返回類型
System.out.println("");
A a = new A();//TODO 方法中使用到
return a;
}
}
class A {}
class B {}
8.2: 繼承(泛化 Generalization)
泛化關(guān)系實際上就是繼承關(guān)系,依賴關(guān)系的特例。
image
public class Generalization extends Base {
@Override
public void get(Object oId) {
}
@Override
public void put(Object oName) {
}
}
abstract class Base {
abstract public void get(Object oId);
abstract public void put(Object oName);
}
8.3: 實現(xiàn)(Realization)
實現(xiàn)關(guān)系實際上就是 A類 實現(xiàn) B接口,依賴關(guān)系的特例。
image
public class Implementation implements Base {
@Override
public void init() {
System.out.println("init");
}
}
interface Base {
void init();
}
8.3: 關(guān)聯(lián)(Association)
類與類之間的關(guān)系,依賴關(guān)系的特例。
關(guān)聯(lián)具有導航性:即雙向關(guān)系或單向關(guān)系。
image
public class Person {
private IDCard idCard;
}
class IDCard {
//private Person person;
}
8.4: 聚合(Aggregation)
表示的是整體和部分的關(guān)系,整體與部分可以分開,關(guān)聯(lián)關(guān)系的特例。
聚合關(guān)系是關(guān)聯(lián)關(guān)系的特例,所以他具有關(guān)聯(lián)的導航性與多重性。
image
public class Computer {
private Mouse mouse;
private Keyboard keyboard;
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void setKeyboard(Keyboard keyboard) {
this.keyboard = keyboard;
}
}
class Mouse {}
class Keyboard {}
組合(Composite)
整體與部分的關(guān)系,但是整體與部分不可以分開,關(guān)聯(lián)關(guān)系的特例。
級聯(lián)刪除就是組合關(guān)系。
image
public class Computer {
private CPU cpu = new CPU();
private SSD ssd = new SSD();
}
class CPU {}
class SSD {}