01 設計模式原則
阿新 • • 發佈:2021-08-30
設計模式
設計模式是對軟體設計中普遍存在(反覆出現)的各種問題,所提出的解決方案
一、 設計模式的目的:
設計模式是為了讓程式(軟體),具有更好
- 程式碼重用性(即:相同功能的程式碼,不用多次編寫)
- 可讀性(即:程式設計規範性,便於其他程式設計師的閱讀和理解)
- 可擴充套件性(即:當需要增加新的功能時,非常的方便)
- 可靠性(即:當我們在增加新的功能後,對原來的功能沒有影響)
- 使程式程式高內聚,低耦合的特性
二、設計模式的七大原則
- 單一職責原則
- 介面隔離原則
- 依賴倒轉原則
- 里氏替換原則
- 開閉原則ocp
- 迪米特法則
- 合成複用原則
2.1 單一職責原則
基本介紹:
對類來說的,即一個類應該只負責一項職責,如類A負責兩個不同職責:職責1,職責2.當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解為A1,A2
public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("摩托車"); vehicle.run("飛機"); } } class Vehicle{ //1.在這個方法裡 違反了單一職責原則 //2.解決方案:根據交通工具執行方法不同,分解成不同類即可 public void run(String vehicle){ System.out.println(vehicle + "在公路上執行"); } } public class SingleResponsibility2 { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); roadVehicle.run("車"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飛機"); WaterVehicle waterVehicle = new WaterVehicle(); waterVehicle.run("船"); } } //方案2的分析 //1.遵守單一職責原則 //2.但是這樣改動很大,將類分解,同時修改客戶端 //2.改進:直接修改Vehicle類,改動的程式碼會比較少=>方案3 class RoadVehicle{ public void run(String vehicle){ System.out.println(vehicle+"在公路上執行"); } } class AirVehicle{ public void run(String vehicle){ System.out.println(vehicle+"在天上執行"); } } class WaterVehicle{ public void run(String vehicle){ System.out.println(vehicle+"在水上執行"); } } public class SingleResponsibility3 { public static void main(String[] args) { Vehicle2 vehicle2 = new Vehicle2(); vehicle2.run("車"); vehicle2.runAir("飛機"); vehicle2.run("船"); } } //方案3的分析 //1.這種修改方法沒有對原來的類做大的修改,只是增加方法 //2.沒有在類這個級別上遵守單一職責原則,但是在方法上仍然遵守 class Vehicle2{ public void run(String vehicle) { System.out.println(vehicle + "在公路上執行"); } public void runAir(String vehicle) { System.out.println(vehicle + "在天上執行"); } public void runWater(String vehicle) { System.out.println(vehicle + "在水上執行"); } }
單一職責注意事項和細節
- 降低類的複雜度,一個類只負責一項職責
- 提高類的可讀性,可維護性
- 降低變更引起的風險
- 通常情況下,我們應當遵守單一職責原則,只有邏輯足夠簡單,才可以在程式碼違反單一職責原則;只有類中方法數量足夠少,可以在方法級別保持單一職責原則
2.2 介面隔離原則
基本介紹:
客戶端不應該依賴它不需要的介面,即一個類對另一類的依賴應該建立在最小的介面上
類A通過Interface1 依賴類B,類C通過Interface1 依賴類D,如果介面Interface1 對於類A不是最小介面,那麼類B和類D必須去實現它們不需要
的方法
public class Segregation1 { } interface Interface1{ void operation1(); void operation2(); void operation3(); void operation4(); void operation5(); } class B implements Interface1{ @Override public void operation1() { System.out.println("B實現了operation1"); } @Override public void operation2() { System.out.println("B實現了operation2"); } @Override public void operation3() { System.out.println("B實現了operation3"); } @Override public void operation4() { System.out.println("B實現了operation4"); } @Override public void operation5() { System.out.println("B實現了operation5"); } } class D implements Interface1{ @Override public void operation1() { System.out.println("D實現了operation1"); } @Override public void operation2() { System.out.println("D實現了operation2"); } @Override public void operation3() { System.out.println("D實現了operation3"); } @Override public void operation4() { System.out.println("D實現了operation4"); } @Override public void operation5() { System.out.println("B實現了operation5"); } } class A{//A類 通過介面Interface1 依賴(使用)B類,但是隻會用到1,2,3方法 public void depend1(Interface1 i){ i.operation1(); } public void depend2(Interface1 i){ i.operation2(); } public void depend3(Interface1 i){ i.operation3(); } } class C{//C類 通過介面Interface1 依賴(使用)D類,但是隻會用到1,4,5方法 public void depend1(Interface1 i){ i.operation1(); } public void depend4(Interface1 i){ i.operation4(); } public void depend5(Interface1 i){ i.operation5(); } }
按隔離原則應當這樣處理:
將Interface1 拆分成幾個介面,類A和類C分別與它們需要的介面建立依賴關係,也就是隔離原則
根據實際情況,拆分成3個介面
public class Segregation1 {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());//A類通過介面去依賴B類
a.depend2(new B());
C c = new C();
c.depend1(new D());
}
}
//介面1
interface Interface1{
void operation1();
}
//介面2
interface Interface2{
void operation2();
void operation3();
}
//介面3
interface Interface3{
void operation4();
void operation5();
}
class B implements Interface1,Interface2{
@Override
public void operation1() {
System.out.println("B實現了operation1");
}
@Override
public void operation2() {
System.out.println("B實現了operation2");
}
@Override
public void operation3() {
System.out.println("B實現了operation3");
}
}
class D implements Interface1,Interface3{
@Override
public void operation1() {
System.out.println("D實現了operation1");
}
@Override
public void operation4() {
System.out.println("D實現了operation4");
}
@Override
public void operation5() {
System.out.println("B實現了operation5");
}
}
class A{//A類 通過介面Interface1 Interface2依賴(使用)B類,但是隻會用到1,2,3方法
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface2 i){
i.operation2();
}
public void depend3(Interface2 i){
i.operation3();
}
}
class C{//C類 通過介面Interface1 依賴(使用)D類,但是隻會用到1,4,5方法
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface3 i){
i.operation4();
}
public void depend5(Interface3 i){
i.operation5();
}
}
2.3 依賴倒轉原則
- 高層模組不應該依賴底層模組,二者都應該依賴其抽象
- 抽象不應該依賴細節,細節應該依賴抽象
- 依賴倒轉(倒置)的中心思想是面向介面抽象
- 依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的多,以抽象為基礎搭建的架構比以細節為基礎的架構要穩定的多。在java中,抽象指的是介面或抽象類,細節就是具體的實現類
- 使用介面或抽象類的目的是定好規範,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成
建立一個Person類用來接收資訊
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public String getInfo(){
return "電子郵件資訊:hello";
}
}
//Person接收訊息的功能
//方式1 分析
//1.簡單 比較容易想到
//2.如果我們獲取的物件是微信,簡訊等等,則新增類,同時Person也需要增加相應的方法
//3.引入一個抽象的介面 IReceiver 表示接收者,這樣Person類與介面IReceiver發生依賴
//因為 Email ,weixin 等等屬於接受的範圍,各自實現IReceiver 介面就ok,這樣我們就符合依賴倒轉原則
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
}
2.3.1 依賴關係傳遞的三種方式
- 介面傳遞
- 構造方法傳遞
- setter方式傳遞
//方式1:通過介面傳遞實現依賴
interface IOpenAndClose{
public void open(ITV tv);//抽象方式,接受介面
}
interface ITV{
public void play();
}
class ChangHong implements ITV{
@Override
public void play() {
System.out.println("開啟電視");
}
}
class OpenAndClose implements IOpenAndClose{
@Override
public void open(ITV tv) {
tv.play();
}
}
//方式2 通過構造方法依賴傳遞
interface IOpenAndClose{
public void open();//抽象方式,接受介面
}
interface ITV{
public void play();
}
class ChangHong implements ITV{
@Override
public void play() {
System.out.println("開啟電視");
}
}
class OpenAndClose implements IOpenAndClose{
public ITV tv;
public OpenAndClose(ITV tv){
this.tv = tv;
}
@Override
public void open() {
tv.play();
}
}
//方式3 通過setter方法傳遞
interface IOpenAndClose{
public void open();//抽象方式,接受介面
public void setTv();
}
interface ITV{
public void play();
}
class ChangHong implements ITV{
@Override
public void play() {
System.out.println("開啟電視");
}
}
class OpenAndClose implements IOpenAndClose{
private ITV tv;
@Override
public void setTv() {
this.tv = tv;
}
@Override
public void open() {
tv.play();
}
}
依賴倒轉原則的注意事項和細節
- 低層模組儘量都要有抽象類或介面,或者兩者都有,程式穩定性更好
- 變數的宣告型別儘量是抽象類或介面,這樣我們的變數引用或實際物件間,就存在一個緩衝層,利於程式擴充套件和優化
- 繼承時遵循里氏替換原則
2.4 里氏替換原則
- 所有引用基類的地方必須能透明地使用其子類地物件
- 在使用繼承時,遵循里氏替換原則,在子類中儘量不要重寫父類地方法
- 里氏替換原則告訴我們,繼承實際上讓兩個耦合性增強了,在適當地情況下,可以通過聚合,組合,依賴來解決問題
public class Liskov {
public static void main(String[] args) {
B b = new B();
//B類不在繼承A ,因此呼叫不會再func1是求減 法
}
}
//建立一個基礎基類
class Base{
//更加基礎地方法和成員
public int func1(int num1,int num2){
return num1 - num2;
}
}
class A extends Base{
public int func1(int a,int b){
return a-b;
}
}
class B extends Base{
private A a = new A();
//B類使用A類地方法,使用組合關係
public int func1(int a,int b){
return a+b;
}
public int func2(int a,int b){
return func1(a,b) + 9;
}
//仍然想要使用A類地方法
public int func3(int a,int b){
return this.a.func1(a,b);
}
}
2.5 開閉原則
- 一個類、模組和函式應該對擴充套件開發(對提供方),對修改關閉(對使用方)。用抽象構建框架,用實現擴充套件細節
- 當軟體需要變化時,儘量通過擴充套件軟體實體地行為來實現變化,而不是通過修改已有程式碼來實現變化
- 程式設計中遵循其他原則,以及使用設計模式地目的就是遵循開閉原則
public class Ocp {
public static void main(String[] args) {
Graph graph = new Graph();
graph.drawShape(new Rectangle());
}
}
class Graph{
public void drawShape(Shape s){
s.draw();
}
}
abstract class Shape{
int m_type;
public abstract void draw();
}
class Rectangle extends Shape{
Rectangle(){
m_type = 2;
}
@Override
public void draw() {
System.out.println("繪製矩形");
}
}
2.6 迪米特法則
-
一個物件應該對其他物件保持最少的瞭解
-
類與類關係越密切,耦合度越大
-
迪米特法則又叫最少知道原則,即一個類對自己依賴的類知道的越少越好。對外除了提供的公共方法,不對外洩露任何資訊
-
迪米特法則還有個更簡單的定義:只與直接的朋友通訊
-
直接的朋友:每個物件都會與其他物件有耦合關係,只要兩個物件之間有耦合關係,我們就說這兩個物件之間是朋友關係。(耦合的方式很多,依賴,關聯,組合,聚合等。其中,我們稱出現成員變數,方法引數,方法返回值中的類為直接的朋友,而出現在區域性變數中的類不是直接的朋友。也就是說,陌生的類最好不要以區域性變數的形式出現在類的內部。
public class Demeter1 {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
//學校總共員工
class Employee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//學院總共員工
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
class CollegeManager{
//返回學院的所有員工
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("id=" +i);
list.add(emp);
}
return list;
}
}
class SchoolManager{
//返回學校總部的員工
List<CollegeEmployee> list = new ArrayList<>();
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("id=" +i);
list.add(emp);
}
return list;
}
//該方法完成輸出學校總部和學院員工資訊
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());
}
}
}
public class Demeter1 {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
//學校總共員工
class Employee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//學院總共員工
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
class CollegeManager{
//返回學院的所有員工
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("id=" +i);
list.add(emp);
}
return list;
}
//該方法完成輸出學校員工資訊
void printAllEmployee(){
//獲取到學院員工
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("- --學院員工-----.-----");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
class SchoolManager{
//返回學校總部的員工
List<CollegeEmployee> list = new ArrayList<>();
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("id=" +i);
list.add(emp);
}
return list;
}
//該方法完成輸出學校總部和學院員工資訊
void printAllEmployee(CollegeManager sub){
//分析問題
//1.將輸出學院的員工方法,封裝到CollegeManager
sub.printAllEmployee();
//獲取到學院員工
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());
}
}
}
迪米特法則注意事項和細節
- 迪米特法則的核心是降低類之間的耦合
- 注意:由於每個類都減少了不必要的依賴,因此迪米特法則只是要求降低類間(物件間)耦合關係,並不是要求完全沒有依賴關係
2.7 合成複用原則
儘量使用合成/聚成的方式,而不是使用繼承
設計原則核心思想:
- 找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的程式碼混在一起
- 針對介面程式設計,而不是針對實現程式設計
- 為了互動物件之間的鬆耦合設計而努力