OOP的七大原則 OCP(Open Closed Principle),開放封閉原則 :軟體實體應該擴展開放、修改封閉。 實現:合理劃分構件,一種可變性不應當散落在代碼的很多角落裡,而應當被封裝到一個對象里;一種可變性不應當與另一個可變性混合在一起。 DIP(Dependency Inversion ...
OOP的七大原則
OCP(Open-Closed Principle),開放封閉原則:軟體實體應該擴展開放、修改封閉。
實現:合理劃分構件,一種可變性不應當散落在代碼的很多角落裡,而應當被封裝到一個對象里;一種可變性不應當與另一個可變性混合在一起。
DIP(Dependency Inversion Principle),依賴倒置原則:擺脫面向過程編程思想中高層模塊依賴於低層實現,抽象依賴於具體細節。OOP中要做到的是,高層模塊不依賴於低層模塊實現,二者都依賴於抽象;抽象不依賴於具體實現細節,細節依賴於抽象。
實現:應該通過抽象耦合的方式,使具體類最大可能的僅與其抽象類(介面)發生耦合;程式在需要引用一個對象時,應當儘可能的使用抽象類型作為變數的靜態類型,這就是針對介面編程的含義。
LSP(Liskov Substitution Principle),Liskov替換原則:繼承思想的基礎。“只有當衍生類可以替換掉基類,軟體單位的功能不會受到影響時,基類才真正被覆用,而衍生類也才能夠在基類的基礎上增加新的行為。”
ISP(Interface Insolation Principle),介面隔離原則:介面功能單一,避免介面污染。
實現:一個類對另外一個類的依賴性應當是建立在最小的介面上的。使用多個專門的介面比使用單一的總介面要好。
SRP(Single Resposibility Principle),單一職責原則:就一個類而言,應該僅有一個引起它變化的原因。 如果一個類的職責過多,就等於把這些職責耦合在一起,一個職責的變化可能會抑止這個類完成其他職責的能力。
CARP(Composite/Aggregate Reuse Principle),合成/聚合復用原則:設計模式告訴我們對象委托優於類繼承,從UML的角度講,就是關聯關係優於繼承關係。儘量使用合成/聚合、儘量不使用繼承。
實現:在一個新的對象裡面使用一些已有的對象,使之成為新對象的一部分,以整合其功能。
LoD(Law Of Demeter or Principle of Least Knowledge),迪米特原則或最少知識原則:就是說一個對象應當對其他對象儘可能少的瞭解。即只直接與朋友通信,或者通過朋友與陌生人通信。
工廠模式
工廠模式的核心本質:
1、 實例化的對象不是用new, 而用工廠方法代替
2、 將選擇的實現類, 創建對象統一管理和控制, 從而將調用者跟我們的實現類解耦
三種模式
1、 簡單工廠模式
2、 工廠方法模式
3、 抽象工廠模式
遵守的OOP原則
1、開閉原則
2、 依賴倒置原則
3、 迪米特法則
簡單工廠模式
簡單工廠模式又稱為靜態工廠模式
建立一個簡單的汽車介面
public interface Car {
void name();
}
實現這個介面 以寶馬和賓士為例子
public class BaoMa implements Car {
@Override
public void name() {
System.out.println("我叫寶馬");
}
}
public class BenChi implements Car {
@Override
public void name() {
System.out.println("我叫賓士");
}
}
正常情況下, 我們需要使用賓士和寶馬 這兩個實現類需要用 new 實例化一個對象來使用這個方法
public class Main {
public static void main(String[] args) {
BaoMa baoMa = new BaoMa();
BenChi benChi = new BenChi();
baoMa.name();
benChi.name();
}
}
這樣每調用一個新的汽車類型都需要new一個實例化對象,為了減少對new 的實例化對象的使用 ,用工廠方法來替代, 我們可以使用簡單工廠模式,利用Java的static關鍵字,創建一個工廠類,利用變數name 來判斷需要獲取的是哪一個車型
public class CarFactory {
public static Car getCar(String name) {
Car car = null;
if (name.equals("寶馬")) {
car = new BaoMa();
} else if (name.equals("賓士")) {
car = new BenChi();
}
return car;
}
}
CarFactory.getCar("寶馬").name();
CarFactory.getCar("賓士").name();
工廠模式
雖然簡單的工廠模式使用了工廠方法來調用實體類, 但是每當我們修改或者新增汽車的實現類時候都需要修改我們的工廠類,這是不滿足開閉原則的。
我們可以將工廠定為介面,這樣通過實現不同的工廠類來獲取不同車型,並且再也不需要通過關鍵字來指定獲取的車型(解耦)
public interface ICarFactory {
Car getCar();
}
實現不同的車型工廠
public class BaoMaFactory implements ICarFactory {
@Override
public Car getCar() {
return new BaoMa();
}
}
Car baoMa = new BaoMaFactory().getCar();
Car benChi = new BenChiFactory().getCar();
baoMa.name();
benChi.name();
並且 ,對於新增的汽車類型, 我們只需要實現相對應的工廠介面和車類介面, 降低了代碼之間的耦合度。
但是也可以明顯的感覺到,工廠模式相對於簡單工廠模式代碼的複雜度,代碼量都有所增加,實際開發中常常選擇簡單工廠模式而不是工廠模式。
單例模式
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模
式,它提供了一種創建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供
了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象,從系統啟動到終止,整個
過程只會產生一個實例。
餓漢式
在類載入的時候就初始化對象。
public class Hungry {
private Hungry(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(Hungry::getInstance).start();
}
}
}
利用java的classloader機制避免了 多線程問題,而且不需要加鎖,使用效率會很高,缺點載入的時候就初始化,當這個類所需要的的記憶體空間很大,會造成資源的浪費。
懶漢式(線程不安全)
和餓漢式不同,在需要使用這個類的時候在初始化
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName()+"ok");
}
private static Lazy LAZY;
public static Lazy getInstance() {
if (LAZY == null) {
LAZY = new Lazy();
}
return LAZY;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(Lazy::getInstance).start();
}
}
}
因為沒有加鎖,每次獲取線程的運行結果都不一樣。優點是,運行效率高(沒加鎖),缺點是多 線程環境不安全。
懶漢式(雙重驗證DCL)
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static Lazy LAZY;
public static Lazy getInstance() {
if (LAZY == null) {
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(Lazy::getInstance).start();
}
}
}
採用雙重驗證降低了加鎖帶來的性能開銷,並且利用volatile關鍵字防止指令重排的問題
靜態內部類
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return innerClass.HOLDER;
}
public static class innerClass {
public static final Holder HOLDER = new Holder();
}
}
在餓漢式的基礎上,通過靜態類延遲載入來實現懶載入,性能高,而且線程安全,實現難比DCL小
枚舉類型
事實上, 上文提到的單例模式類型都是不安全,因為java有著強大的反射機制可以破壞單例唯一性。
以DCL為例:
package Singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author :beibei
* @date :Created in 2020/4/20 9:30
* @description :
*/
public class Lazy {
private Lazy() {
// System.out.println(Thread.currentThread().getName()+"ok");
synchronized (Lazy.class) {
if (LAZY != null) {
throw new RuntimeException("不要破壞單例");
}
}
}
private volatile static Lazy LAZY;
public static Lazy getInstance() {
if(LAZY == null){
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
public static void main(String[] args) throws Exception{
// for (int i = 0; i < 10; i++) {
// new Thread(Lazy::getInstance).start();
// }
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy newInstance = declaredConstructor.newInstance();
Lazy instance = Lazy.getInstance();
System.out.println(newInstance);
System.out.println(instance);
}
}
都可利用反射setAccessible等方式破壞私有類,破壞單例。
閱讀反射的部分源碼 可以看到這樣一段代碼
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
當類為枚舉類型時,會拋出一個IllegalArgumentException錯誤,來防止反射對枚舉類型類的單例破壞
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance() {
return INSTANCE;
}
}
所以,首推使用反射進行單例模式的實現。
建造者模式
- 建造者提供了創建對象的最佳方式。
- 定義: 將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
- 主要作用: 在用戶不知道對象的建造過程和細節的情況下就可以直接創建複雜的對象。
- 用戶只需要給出複雜對象的類型和內容,建造者模式負責按照順序創建複雜對象(把內部的建造過程和細節隱藏起來)
指揮者(Director)直接和客戶(Client)進行需求溝通,溝通後指揮者將客戶創建產品的需求劃分為各個部件的建造請求(Builder),將各個部件的建造請求委派到具體的建造者(ConcreteBuilder),各個具體建造者負責進行產品部件的構建,最終構建成具體產品(Product)。
Builder類
public abstract class Builder {
abstract void buildA();
abstract void buildB();
abstract void buildC();
abstract Product getProduct();
}
Director類
public class Director {
public Product build(Builder builder) {
builder.buildA();
builder.buildB();
builder.buildC();
return builder.getProduct();
}
}
Product類
public class Product {
private String BuildA;
private String BuildB;
private String BuildC;
public void setBuildA(String buildA) {
BuildA = buildA;
}
public void setBuildB(String buildB) {
BuildB = buildB;
}
@Override
public String toString() {
return "Product{" +
"BuildA='" + BuildA + '\'' +
", BuildB='" + BuildB + '\'' +
", BuildC='" + BuildC + '\'' +
'}';
}
public void setBuildC(String buildC) {
BuildC = buildC;
}
}
工人(createBuilder)類
public class Worker extends Builder {
private Product product;
public Worker() {
product = new Product();
}
@Override
void buildA() {
product.setBuildA("buildA");
System.out.println("構建buildA");
}
@Override
void buildB() {
product.setBuildB("buildB");
System.out.println("構建buildB");
}
@Override
void buildC() {
product.setBuildC("buildC");
System.out.println("構建buildC");
}
@Override
Product getProduct() {
return product;
}
}
導演類 Director 在 Builder模式中具有很重要的作用,它用於指導具體構建者如何構建產品,控制調用先後次序,並向調用者返回完整的產品類
同樣的我們可以進行一些改造,利用鏈式編程的方式省略了Director類,用戶直接操作。
public abstract class Builder {
abstract Builder buildA(String msg);
abstract Builder buildB(String msg);
abstract Builder buildC(String msg);
abstract Product getProduct();
}
package Builder.demo2;
/**
* @author :beibei
* @date :Created in 2020/4/21 0:11
* @description :
*/
public class Worker extends Builder {
private Product product;
public Worker() {
product = new Product();
}
@Override
Builder buildA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder buildB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder buildC(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
public class Main {
public static void main(String[] args) {
Worker worker = new Worker();
worker.buildA("beibei").buildB("beibei2").buildC("beibei3");
Product product = worker.getProduct();
System.out.println(product.toString());
}
}
雖然用戶可以直接操作生成的產品但是不過這樣的方式在build的產品類多的時候,可讀性可見性差。
原型模式
- 克隆
- Prototype
- Cloneable
- clone() 方法
淺克隆
以視頻對象為例,有著名字(name),創建時間(createTime)屬性,實現Cloneable介面
public class Video implements Cloneable {
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Video() {
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Video v1 = new Video("羅教授南波灣", date);
System.out.println("v1=>" + v1);
System.out.println("v1 hashcode =>" + v1.hashCode());
// clone 克隆
Video clone = (Video) v1.clone();
System.out.println("clone =>" + clone);
System.out.println("clone hashcode=>" + clone.hashCode());
}
}
結果:
v1=>Video{name='羅教授南波灣', createTime=Tue Apr 21 15:59:48 CST 2020}
v1 hashcode =>1735600054
clone =>Video{name='羅教授南波灣', createTime=Tue Apr 21 15:59:48 CST 2020}
clone hashcode=>21685669
可以看到,雖然video的內容一樣,但是他們的hashCode不一樣,是不一樣的對象。
我們修改date的時間
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Video v1 = new Video("羅教授南波灣", date);
Video clone = (Video) v1.clone();
System.out.println("v1=>" + v1);
System.out.println("clone =>" + clone);
date.setTime(21113333);
System.out.println("-------------");
System.out.println("v1=>" + v1);
System.out.println("clone =>" + clone);
}
}
結果:
v1=>Video{name='羅教授南波灣', createTime=Tue Apr 21 16:10:49 CST 2020}
clone =>Video{name='羅教授南波灣', createTime=Tue Apr 21 16:10:49 CST 2020}
-------------
v1=>Video{name='羅教授南波灣', createTime=Thu Jan 01 13:51:53 CST 1970}
clone =>Video{name='羅教授南波灣', createTime=Thu Jan 01 13:51:53 CST 1970}
可以看見 ,不光光原本的v1對象的時間對象發生了改變,而且clone的對象也發生了該變。說明原本對象和clone對象都還共同依賴著 時間對象
所以,淺克隆僅僅拷貝對象本身(包括對象中的基本變數),而不拷貝對象包含的引用指向的對象。
深克隆
淺克隆雖然實現了clone的目標,但是我們如果希望clone對象完全和原對象沒關係,即上文中的克隆對象和時間對象不關聯,修改了原對象時間也不會對克隆對象時間造成影響。那麼我們只需要重寫對象Cloneable的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Video clone = (Video) super.clone();
clone.createTime = (Date) this.createTime.clone();
return clone;
}
同樣的還可以使用序列化和反序列操作,生成完全新的對象,那樣新對象和原本對象也無任何關係。
public static Object cloneObject(Object obj) throws IOException, ClassNotFoundException{
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(obj);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in =new ObjectInputStream(byteIn);
return in.readObject();
}