Java 23種設計模式 - 結構型模式7種

Java 23種設計模式 - 結構型模式7種

1 適配器模式

適配器模式把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。

優點

  1. 將目標類和適配者類解耦
  2. 增加了類的透明性和復用性,將具體的實現封裝在適配者類中,對于客戶端類來說是透明的,而且提高了適配者的復用性
  3. 靈活性和擴展性都非常好,符合開閉原則

1.1 例子

整個流程就是通過 SmallToBig(適配器)SmallPort(適配者) 轉換為 BigPort(目標) 的子類

/*** 投影儀支持的大口*/
public interface BigPort {public void userBigPort();//使用的大口
}
/*** 電腦的小口*/
public interface SmallPort {public void userSmallPort();//使用小口
}
/*** 適配器模式*/
public class SmallToBig implements BigPort{private SmallPort smallPort;//小口public SmallToBig(SmallPort smallPort){//獲得小口this.smallPort=smallPort;}public void userBigPort() {this.smallPort.userSmallPort();    //使用小口}}

public class Client {public static void main(String[] args) {SmallPort smallPort = new SmallPort() {//電腦自帶小口public void userSmallPort() {System.out.println("使用的是電腦小口");}};//需要一個大口才可以投影,小口轉換為大口BigPort bigPort=new SmallToBig(smallPort);bigPort.userBigPort();//電腦小口工作中    實現了適配}    
}

2 裝飾器模式(Decorator Pattern)

通常給對象添加功能,要么直接修改對象添加相應的功能,要么派生子類來擴展,抑或是使用對象組合的方式。顯然,直接修改對應的類的方式并不可取,在面向對象的設計中,我們應該盡量使用組合對象而不是繼承對象來擴展和復用功能,裝飾器模式就是基于對象組合的方式的。

裝飾器模式以對客戶端透明的方式動態地給一個對象附加上了更多的責任。換言之,客戶端并不會角色對象在裝飾前和裝飾后有什么不同。裝飾器模式可以在不用創建更多子類的情況下,將對象的功能加以擴展。

Decorator裝飾器,顧名思義,就是動態地給一個對象添加一些額外的職責,就好比為房子進行裝修一樣。因此,裝飾器模式具有如下的特征:

  1. 它必須具有一個裝飾的對象。
  2. 它必須擁有與被裝飾對象相同的接口。
  3. 它可以給被裝飾對象添加額外的功能。

用一句話總結就是:保持接口,增強性能。

裝飾器通過包裝一個裝飾對象來擴展其功能,而又不改變其接口,這實際上是基于對象的適配器模式的一種變種。它與對象的適配器模式的異同點如下。

  • 相同點:都擁有一個目標對象。
  • 不同點:適配器模式需要實現另外一個接口,而裝飾器模式必須實現該對象的接口。

2.1 例子

現在有這么一個場景:

  1. 有一批廚師,簡單點吧,就全是中國廚師,他們有一個共同的動作是做晚飯
  2. 這批廚師做晚飯前的習慣不同,有些人喜歡做晚飯前洗手、有些人喜歡做晚飯前洗頭
public interface Cook {public void cookDinner();
}
public class ChineseCook implements Cook {@Overridepublic void cookDinner() {System.out.println("中國人做晚飯");}}
public abstract class FilterCook implements Cook {protected Cook cook;
}
public class WashHandsCook extends FilterCook {public WashHandsCook(Cook cook) {this.cook = cook;}@Overridepublic void cookDinner() {System.out.println("先洗手");cook.cookDinner();}}
public class WashHearCook extends FilterCook {public WashHearCook(Cook cook) {this.cook = cook;}@Overridepublic void cookDinner() {System.out.println("先洗頭");cook.cookDinner();}}
@Test
public void testDecorate() {Cook cook0 = new WashHandsCook(new ChineseCook());Cook cook1 = new WashHearCook(new ChineseCook());cook0.cookDinner();cook1.cookDinner();
}

結果:

先洗手
中國人做飯
先洗頭
中國人做飯

簡單的一個例子,實現了裝飾器模式的兩個功能點:

  1. 客戶端只定義了Cook接口,并不關心具體實現
  2. 給Chinese增加上了洗頭和洗手的動作,且洗頭和洗手的動作,可以給其他國家的廚師類復用

2.2 字節輸入流InputStream

在這里插入圖片描述

InputStream是一個頂層的接口,文章開頭就說,裝飾器模式是繼承關系的一種替代方案,看一下為什么:

  1. InputStream假設這里寫了兩個實現類,FileInputStream,ObjectInputStream分別表示文件字節輸入流,對象字節輸入流
  2. 現在我要給這兩個輸入流加入一點緩沖功能以提高輸入流效率,使用繼承的方式,那么就寫一個BufferedInputStream,繼承FileInputStream,ObjectInputStream,給它們加功能
  3. 現在我有另外一個需求,需要給這兩個輸入流加入一點網絡功能,那么就寫一個SocketInputStream,繼承繼承FileInputStream,ObjectInputStream,給它們加功能

這樣就導致兩個問題:

  1. 因為我要給哪個類加功能就必須繼承它,比如我要給FileInputStream,ObjectInputStream加上緩沖功能、網絡功能就得擴展出2*2=4個類,更多的以此類推,這樣勢必導致類數量不斷膨脹
  2. 代碼無法復用,給FileInputStream,ObjectInputStream加入緩沖功能,本身代碼應該是一樣的,現在卻必須繼承完畢后把一樣的代碼重寫一遍,多此一舉,代碼修改的時候必須修改多個地方,可維護性很差

所以,這個的時候我們就想到了一種解決方案:

  1. 在要擴展的類比如BufferedInputStream中持有一個InputStream的引用,在BufferedInputStream調用InputStream中的方法,這樣擴展的代碼就可以復用起來
  2. 將BufferedInputStream作為InputStream的子類,這樣客戶端只知道我用的是InputStream而不需要關心具體實現,可以在客戶端不知情的情況下,擴展InputStream的功能,加上緩沖功能

這就是裝飾器模式簡單的由來,一切都是為了解決實際問題而誕生。下一步,根據UML圖,我們來劃分一下裝飾器模式的角色。

  1. InputStream是一個抽象構件角色:
  2. ByteArrayInputStream、FileInputStream、ObjectInputStream、PipedInputStream都是具體構建角色
  3. FilterInputStream無疑就是一個裝飾角色,因為FilterInputStream實現了InputStream內的所有抽象方法并且持有一個InputStream的引用
  4. 具體裝飾角色就是InflaterInputStream、BufferedInputStream、DataInputStream

搞清楚具體角色之后,我們就可以這么寫了

public static void main(String[] args) throws Exception
{File file = new File("D:/aaa.txt");InputStream in0 = new FileInputStream(file);InputStream in1 = new BufferedInputStream(new FileInputStream(file)); InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
}

我們這里實例化出了三個InputStream的實現類:

  1. in0這個引用指向的是new出來的FileInputStream,這里簡單構造出了一個文件字節輸入流
  2. in1這個引用指向的是new出來的BufferedInputStream,它給FileInputStream增加了緩沖功能,使得FileInputStream讀取文件的內容保存在內存中,以提高讀取的功能
  3. in2這個引用指向的是new出來的DataInputStream,它也給FileInputStream增加了功能,因為它有DataInputStream和BufferedInputStream兩個附加的功能

同理,我要給ByteArrayInputStream、ObjectInputStream增加功能,也可以使用類似的方法,整個過程中,最重要的是要理解幾個問題:

  1. 哪些是具體構建角色、哪些是具體裝飾角色,尤其是后者,區分的關鍵就是,角色中是否持有頂層接口的引用
  2. 每個具體裝飾角色有什么作用,因為只有知道每個具體裝飾角色有什么作用后,才可以知道要裝飾某個功能需要用哪個具體裝飾角色
  3. 使用構造方法的方式將類進行組合,給具體構建角色加入新的功能

2.3 字符輸入流Reader

根據UML,分析一下每個角色:

  1. 抽象構建角色,毫無疑問,由Reader來扮演,它是一個抽象類,沒有具體功能
  2. 具體構建角色,由InputStreamReader、CharArrayReader、PipedReader、StringReader來扮演
  3. 裝飾角色,由FilterReader來扮演,BufferedReader是Reader的子類,且持有Reader的引用,因此這里的BufferedReader是可以被認為是一個裝飾角色的。
  4. 具體裝飾角色,BufferedReader上面提到了扮演了裝飾角色,但是也可以被認為是一個具體裝飾角色。除了BufferedReader,具體裝飾角色還有PushbackReader。

FileReader盡管也在第三行,但是FileReader構不成一個具體裝飾角色,因為它不是BufferedReader的子類也不是FilterReader的子類,不持有Reader的引用。

2.4 半透明裝飾器模式與全透明裝飾器模式

再說一下半透明裝飾器模式與全透明裝飾器模式,它們的區別是:

  • 對于半透明裝飾器模式,裝飾后的類未必有和抽象構件角色同樣的接口方法,它可以有自己擴展的方法
  • 對于全透明裝飾器模式,裝飾后的類有著和抽象構件角色同樣的接口方法, 全透明裝飾器模式是一種比較理想主義的想法,現實中不太可能出現。

比如BufferedInputStream吧,我把FileInputStream裝飾為BufferedInputStream,難道BufferedInputStream就完全沒有自己的行為?比如返回緩沖區的大小、清空緩沖區(這里只是舉個例子,實際BufferedInputStream是沒有這兩個動作的),這些都是InputStream本身不具備的,因為InputStream根本不知道緩沖區這個概念,它只知道定義讀數據相關方法。

所以,更多的我們是采用半透明的裝飾器模式,即 允許裝飾后的類中有屬于自己的方法,因此,前面的I/O代碼示例可以這么改動:

public static void main(String[] args) throws Exception
{File file = new File("D:/aaa.txt");FileInputStream in0 = new FileInputStream(file);BufferedInputStream in1 = new BufferedInputStream(new FileInputStream(file)); DataInputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
}

2.5 裝飾器模式的優缺點

優點

  1. 裝飾器模式與繼承關系的目的都是要擴展對象的功能,但是裝飾器模式可以提供比繼承更多的靈活性。裝飾器模式允許系統動態決定貼上一個需要的裝飾,或者除掉一個不需要的裝飾。繼承關系是不同,繼承關系是靜態的,它在系統運行前就決定了
  2. 通過使用不同的具體裝飾器以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。

缺點

由于使用裝飾器模式,可以比使用繼承關系需要較少數目的類。使用較少的類,當然使設計比較易于進行。但是另一方面,由于使用裝飾器模式會產生比使用繼承關系更多的對象,更多的對象會使得查錯變得困難,特別是這些對象看上去都很像。

2.6 裝飾器模式和適配器模式的區別

其實適配器模式也是一種包裝(Wrapper)模式,它們看似都是起到包裝一個類或對象的作用,但是它們使用的目的非常不一樣:

  1. 適配器模式的意義是要將一個接口轉變成另外一個接口,它的目的是通過改變接口來達到重復使用的目的
  2. 裝飾器模式不要改變被裝飾對象的接口,而是恰恰要保持原有的接口,增強原有接口的功能,或者改變原有對象的處理方法而提升性能

3 代理模式

代理模式的定義很簡單: 給某一對象提供一個代理對象,并由代理對象控制對原對象的引用。

3.1 角色

一個客戶不想或者不能夠直接引用一個對象,可以通過代理對象在客戶端和目標對象之間起到中介作用。代理模式中的角色有:

  1. 抽象對象角色

聲明了目標對象和代理對象的共同接口,這樣一來在任何可以使用目標對象的地方都可以使用代理對象

  1. 目標對象角色

定義了代理對象所代表的目標對象

  1. 代理對象角色

代理對象內部含有目標對象的引用,從而可以在任何時候操作目標對象;代理對象提供一個與目標對象相同的接口,以便可以在任何時候替代目標對象

3.2 spring aop

在Spring的AOP編程中:

  • 如果加入容器的目標對象有實現接口,用JDK代理
  • 如果目標對象沒有實現接口,用Cglib代理。

3.3 靜態代理

接口:IUserDao.java

public interface IUserDao {void save();
}

目標對象:UserDao.java

public class UserDao implements IUserDao {public void save() {System.out.println("----已經保存數據!----");}
}

代理對象:UserDaoProxy.java

public class UserDaoProxy implements IUserDao{//接收保存目標對象private IUserDao target;public UserDaoProxy(IUserDao target){this.target=target;}public void save() {System.out.println("開始事務...");target.save();//執行目標對象的方法System.out.println("提交事務...");}
}

靜態代理總結:

  1. 可以做到在不修改目標對象的功能前提下,對目標功能擴展.
  2. 缺點:因為代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護.

3.4 動態代理

動態代理有以下特點:

  1. 代理對象,不需要實現接口
  2. 代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象(需要我們指定創建代理對象/目標對象實現的接口的類型)
  3. 動態代理也叫做:JDK代理,接口代理

JDK中生成代理對象的API

代理類所在包:java.lang.reflect.Proxy
JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數,完整的寫法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次為:

  • ClassLoader loader :指定當前目標對象使用類加載器,獲取加載器的方法是固定的
  • Class<?>[] interfaces :目標對象實現的接口的類型,使用泛型方式確認類型
  • InvocationHandler :事件處理,執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法作為參數傳入

接口類IUserDao.java以及接口實現類,目標對象UserDao是一樣的,沒有做修改.在這個基礎上,增加一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,然后在測試類(需要使用到代理的代碼)中先建立目標對象和代理對象的聯系,然后代用代理對象的中同名方法

/*** 創建動態代理對象* 動態代理不需要實現接口,但是需要指定接口類型*/
public class ProxyFactory{//維護一個目標對象private Object target;public ProxyFactory(Object target){this.target=target;}//給目標對象生成代理對象public Object getProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("開始事務2");//執行目標對象方法Object returnValue = method.invoke(target, args);System.out.println("提交事務2");return returnValue;}});}}
/*** 測試類*/
public class App {public static void main(String[] args) {// 目標對象IUserDao target = new UserDao();// 【原始的類型 class cn.itcast.b_dynamic.UserDao】System.out.println(target.getClass());// 給目標對象,創建代理對象IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();// class $Proxy0   內存中動態生成的代理對象System.out.println(proxy.getClass());// 執行方法   【代理對象】proxy.save();}
}

總結:代理對象不需要實現接口,但是目標對象一定要實現接口,否則不能用動態代理

3.5 Cglib代理

上面的靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,但是有時候目標對象只是一個單獨的對象,并沒有實現任何的接口,這個時候就可以使用以目標對象子類的方式類實現代理,這種方法就叫做: Cglib代理

Cglib代理,也叫作子類代理或繼承代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展.

  • JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口,如果想代理沒有實現接口的類,就可以使用Cglib實現.
  • Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展java類與實現java接口.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
  • Cglib包的底層是通過使用一個小而塊的字節碼處理框架ASM來轉換字節碼并生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉.

Cglib子類代理實現方法:

  1. 需要引入cglib的jar文件,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
  2. 引入功能包后,就可以在內存中動態構建子類
  3. 代理的類不能為final,否則報錯
  4. 目標對象的方法如果為final/static,那么就不會被攔截,即不會執行目標對象額外的業務方法.
/*** 目標對象,沒有實現任何接口*/
public class UserDao {public void save() {System.out.println("----已經保存數據!----");}
}
/*** Cglib子類代理工廠* 對UserDao在內存中動態構建一個子類對象*/
public class ProxyFactory implements MethodInterceptor{//維護目標對象private Object target;public ProxyFactory(Object target) {this.target = target;}//給目標對象創建一個代理對象public Object getProxyInstance(){//1.工具類Enhancer en = new Enhancer();//2.設置父類en.setSuperclass(target.getClass());//3.設置回調函數en.setCallback(this);//4.創建子類(代理對象)return en.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("開始事務...");//執行目標對象的方法Object returnValue = method.invoke(target, args);System.out.println("提交事務...");return returnValue;}
}
/*** 測試類*/
public class App {@Testpublic void test(){//目標對象UserDao target = new UserDao();//代理對象UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();//執行代理對象的方法proxy.save();}
}

4 外觀模式(Facade Pattern)

用于隱藏系統的復雜性,用戶客戶端與訪問者之間,通過一個外觀類,對客戶端屏蔽復雜的子系統調用。這種類型的設計模式屬于結構型模式。

4.1 例子

  • Facade: 外觀角色
  • SubSystem:子系統角色

public class SystemA {public void operationA(){System.out.println("operation a...");}
}public class SystemB {public void operationB() {System.out.println("operation b...");}
}public class SystemC {public void operationC() {System.out.println("operation c...");}
}
public class Facade {public void wrapOperation() {SystemA a = new SystemA();a.operationA();SystemB b = new SystemB();b.operationB();SystemC c = new SystemC();c.operationC();}
}
public class Client {public static void main(String[] args) {Facade facade = new Facade();facade.wrapOperation();}
}

result:

operation a...
operation b...
operation c...

4.2 模式分析

根據“單一職責原則”,在軟件中將一個系統劃分為若干個子系統有利于降低整個系統的復雜性,一個常見的設計目標是使子系統間的通信和相互依賴關系達到最小,而達到該目標的途徑之一就是引入一個外觀對象,它為子系統的訪問提供了一個簡單而單一的入口。

  • 外觀模式也是“迪米特法則”的體現,通過引入一個新的外觀類可以降低原有系統的復雜度,同時降低客戶類與子系統類的耦合度。
  • 外觀模式要求一個子系統的外部與其內部的通信通過一個統一的外觀對象進行,外觀類將客戶端與子系統的內部復雜性分隔開,使得客戶端只需要與外觀對象打交道,而不需要與子系統內部的很多對象打交道。
  • 外觀模式的目的在于降低系統的復雜程度。
  • 外觀模式從很大程度上提高了客戶端使用的便捷性,使得客戶端無須關心子系統的工作細節,通過外觀角色即可調用相關功能。

外觀模式的缺點

  • 不能很好地限制客戶使用子系統類,如果對客戶訪問子系統類做太多的限制則減少了可變性和靈活性。
  • 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。

5 橋接模式 Bridge Pattern

將抽象化與實現化解耦,使得二者可以獨立變化,例如我們常用的JDBC橋DriverManager一樣,JDBC進行連接數據庫的時候,在各個數據庫之間進行切換,基本不需要動太多的代碼,甚至絲毫不用動,原因就是JDBC提供統一接口,每個數據庫提供各自的實現,用一個叫做數據庫驅動的程序來橋接就行了。

5.1 例子

public interface Sourceable {  public void method();  
}  
public class SourceSub1 implements Sourceable {  @Override  public void method() {  System.out.println("this is the first sub!");  }  
}  public class SourceSub2 implements Sourceable {  @Override  public void method() {  System.out.println("this is the second sub!");  }  
}

public abstract class Bridge {  private Sourceable source;  public void method(){  source.method();  }  public Sourceable getSource() {  return source;  }  public void setSource(Sourceable source) {  this.source = source;  }  
}
public class MyBridge extends Bridge {  public void method(){  getSource().method();  }  
}
public class Client {  public static void main(String[] args) {  Bridge bridge = new MyBridge();  /*調用第一個對象*/  Sourceable source1 = new SourceSub1();  bridge.setSource(source1);  bridge.method();  /*調用第二個對象*/  Sourceable source2 = new SourceSub2();  bridge.setSource(source2);  bridge.method();  }  
}

result:

this is the first sub!
this is the second sub!

6 組合模式 Composite Pattern

組合模式,又叫部分整體模式,用于把一組相似的對象當作一個單一的對象。 允許你將對象組合成樹形結構來表現 “整體/部分” 層次結構,組合能夠讓我們用一致的方式處理個別對象以及對象集合。

6.1 例子

  • Component 抽象構件
  • Composite 樹枝構件
  • Leaf 樹葉構件
public abstract class Component {//個體和整體都具有的共享public void doSomething(){//編寫業務邏輯}
}

public class Composite extends Component {//構件容器private ArrayList<Component> components = new ArrayList<Component>()//增加一個葉子構件或樹枝構件public void add(Component component){this.components.add(component);}//刪除一個葉子構件或樹枝構件public void remove(Component component){this.components.remove(component);}    //獲得分支下的所有葉子構件和樹枝構件public ArrayList<Component> getChildren(){return this.components;}
}
public class Leaf extends Component {/** 
可以覆寫父類方法* public void doSomething(){* * }*/
}

public class Client {public static void main(String[] args) {//創建一個根節點Composite root = new Composite();root.doSomething();//創建一個樹枝構件Composite branch = new Composite();//創建一個葉子節點Leaf leaf = new Leaf();//建立整體root.add(branch);branch.add(leaf);          }//通過遞歸遍歷樹public static void display(Composite root){for(Component c:root.getChildren()){if(c instanceof Leaf){ //葉子節點c.doSomething();}else{ //樹枝節點display((Composite)c);}}}
}

7 享元模式 Flyweight Pattern

用于減少創建對象的數量,以減少內存占用和提高性能。將多個對同一對象的訪問集中起來,不必為每個訪問者創建一個單獨的對象,以此來降低內存的消耗。

比如數據庫的連接池Pool。想想每個連接的特點,我們不難總結出:適用于作共享的一些個對象,他們有一些共有的屬性,url、driverClassName、username、password及dbname,這些屬性對于每個連接來說都是一樣的,所以就適合用享元模式來處理,建一個工廠類,將上述類似屬性作為內部數據,其它的作為外部數據,在方法調用時,當做參數傳進來,這樣就節省了空間,減少了實例的數量。

7.1 例子

  • Shape 形狀
  • Circle 圓

public interface Shape {void draw();
}
public class Circle implements Shape {private String color;private int x;private int y;private int radius;public Circle(String color){this.color = color;     }public void setX(int x) {this.x = x;}public void setY(int y) {this.y = y;}public void setRadius(int radius) {this.radius = radius;}@Overridepublic void draw() {System.out.println("Circle: Draw() [Color : " + color +", x : " + x +", y :" + y +", radius :" + radius);}
}
public class ShapeFactory {private static final HashMap<String, Shape> circleMap = new HashMap<>();public static Shape getCircle(String color) {Circle circle = (Circle)circleMap.get(color);if(circle == null) {circle = new Circle(color);circleMap.put(color, circle);System.out.println("Creating circle of color : " + color);}return circle;}
}
public class Client {private static final String colors[] = { "Red", "Green", "Blue", "White", "Black" };public static void main(String[] args) {for(int i=0; i < 20; ++i) {Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());circle.setX(getRandomX());circle.setY(getRandomY());circle.setRadius(100);circle.draw();}}private static String getRandomColor() {return colors[(int)(Math.random()*colors.length)];}private static int getRandomX() {return (int)(Math.random()*100 );}private static int getRandomY() {return (int)(Math.random()*100);}
}

result:

Creating circle of color : Black
Circle: Draw() [Color : Black, x : 36, y :71, radius :100
Creating circle of color : Green
Circle: Draw() [Color : Green, x : 27, y :27, radius :100
Creating circle of color : White
Circle: Draw() [Color : White, x : 64, y :10, radius :100
Creating circle of color : Red
Circle: Draw() [Color : Red, x : 15, y :44, radius :100
Circle: Draw() [Color : Green, x : 19, y :10, radius :100
Circle: Draw() [Color : Green, x : 94, y :32, radius :100
Circle: Draw() [Color : White, x : 69, y :98, radius :100
Creating circle of color : Blue
Circle: Draw() [Color : Blue, x : 13, y :4, radius :100
Circle: Draw() [Color : Green, x : 21, y :21, radius :100
Circle: Draw() [Color : Blue, x : 55, y :86, radius :100
Circle: Draw() [Color : White, x : 90, y :70, radius :100
Circle: Draw() [Color : Green, x : 78, y :3, radius :100
Circle: Draw() [Color : Green, x : 64, y :89, radius :100
Circle: Draw() [Color : Blue, x : 3, y :91, radius :100
Circle: Draw() [Color : Blue, x : 62, y :82, radius :100
Circle: Draw() [Color : Green, x : 97, y :61, radius :100
Circle: Draw() [Color : Green, x : 86, y :12, radius :100
Circle: Draw() [Color : Green, x : 38, y :93, radius :100
Circle: Draw() [Color : Red, x : 76, y :82, radius :100
Circle: Draw() [Color : Blue, x : 95, y :82, radius :100

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/80345.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/80345.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/80345.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Git clone時出現SSL certificate problem unable to get local issuer certificate

正確解決方法 git config --global http.sslVerify false錯誤解決方法&#xff1a;&#xff08;主要是看錯了嘿嘿&#xff0c;但是如果是 OpenSSL SSL_read: Connection was reset, errno 10054 Failed to connect to github.com port 443: Timed out 原…

DevExpressWinForms-AlertControl-使用教程

文章目錄 AlertControl-使用教程一、將 AlertControl 添加到 Form二、編輯 AlertControl 的 HtmlTemplateHTML Template Editor介紹編輯HTML Template 三、使用AlertControl彈出AlertAlert中的按鈕事件獲取 Alert 標題等信息向Alert傳遞參數 總結源碼 AlertControl-使用教程 一…

制作項目進度表常用的 8 款項目管理工具分享

在數字化管理和高效協作的今天&#xff0c;項目進度表軟件已經成為企業管理不可或缺的工具。無論是中小型企業還是大型機構&#xff0c;都需要通過精準的項目計劃和實時的進度跟蹤來確保業務目標的順利達成。這篇文章將聚焦項目進度表軟件&#xff0c;深入探討市場上8款主流產品…

SecureCRT網絡穿透/代理

場景 公司的辦公VPN軟件只有Windows系統版本&#xff0c;沒有Macos系統版本&#xff0c;而日常開發過程中需要先登錄VPN后&#xff0c;然后才能登錄應用服務器。 目的&#xff1a;Macos系統在使用SecureCRT時&#xff0c;登錄服務器&#xff0c;需要走Parallels Desktop進行網絡…

【計算機網絡-傳輸層】傳輸層協議-TCP核心機制與可靠性保障

&#x1f4da; 博主的專欄 &#x1f427; Linux | &#x1f5a5;? C | &#x1f4ca; 數據結構 | &#x1f4a1;C 算法 | &#x1f152; C 語言 | &#x1f310; 計算機網絡 上篇文章&#xff1a;傳輸層協議-UDP 下篇文章&#xff1a; 網絡層 我們的講解順序是&…

OpenMagnetic的介紹與使用

1. Background OM&#xff08;OpenMagnetic&#xff09;OpenMagnetics&#xff0c;能涵蓋氣隙磁阻&#xff0c;磁導率&#xff0c;鐵芯損耗、磁滯損耗、渦流電流損耗、渦流效應、漏感、溫升的計算與仿真[1]。 鐵損計算模型&#xff1a;改進的Steinmetz方程[2] 氣隙阻抗計算&…

【JVM】從零開始深度解析JVM

本篇博客給大家帶來的是JVM的知識點, 重點在類加載和垃圾回收機制上. &#x1f40e;文章專欄: JavaEE初階 &#x1f680;若有問題 評論區見 ? 歡迎大家點贊 評論 收藏 分享 如果你不知道分享給誰,那就分享給薯條. 你們的支持是我不斷創作的動力 . 王子,公主請閱&#x1f680; …

字符串---Spring字符串基本處理

一、String類的特性 不可變性 String對象一旦創建&#xff0c;內容不可更改&#xff0c;任何修改操作都會生成新對象。字符串常量池 字符串字面量&#xff08;如"abc"&#xff09;直接存儲在常量池中&#xff0c;重復字面量共享同一內存地址。創建方式 雖然都是字符…

26考研——中央處理器_CPU 的功能和基本結構(5)

408答疑 文章目錄 一、CPU 的功能和基本結構CPU 的功能CPU 的基本結構運算器控制器 CPU 的寄存器運算器中的寄存器控制器中的寄存器 八、參考資料鮑魚科技課件26王道考研書 九、總結 一、CPU 的功能和基本結構 CPU 的功能 中央處理器&#xff08;CPU&#xff09;由運算器和控…

傳統數據展示 vs 可視化:誰更打動人心?

數據&#xff0c;每天都在我們身邊流動&#xff1a;從你手機里的健康步數&#xff0c;到企業財報中的營收增長&#xff0c;再到國家發布的經濟指標。但問題是——你怎么“看”這些數據&#xff1f; 過去&#xff0c;我們習慣用表格、文字和報告來展示數據&#xff0c;這種方式…

Base64 編碼原理詳細解析

Base64 編碼是一種常見的數據編碼方式&#xff0c;它將二進制數據轉化為可打印的 ASCII 字符串。Base64 編碼廣泛應用于電子郵件、URL 編碼、HTTP 請求和響應中等場景。它的核心作用是讓二進制數據可以通過僅支持文本的協議或媒介進行傳輸。本文將更深入地探討 Base64 編碼的原…

一周學會Pandas2 Python數據處理與分析-Pandas2數據排序操作

鋒哥原創的Pandas2 Python數據處理與分析 視頻教程&#xff1a; 2025版 Pandas2 Python數據處理與分析 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili Pandas 2提供了多種靈活的數據排序方法&#xff0c;主要針對 DataFrame 和 Series 對象。 1. 按值排序&#xff1a;s…

計算機二級(C語言)已過

非線性結構&#xff1a;樹、圖 鏈表和隊列的結構特性不一樣&#xff0c;鏈表可以在任何位置插入、刪除&#xff0c;而隊列只能在隊尾入隊、隊頭出隊 對長度為n的線性表排序、在最壞情況下時間復雜度&#xff0c;二分查找為O(log2n)&#xff0c;順序查找為O(n)&#xff0c;哈希查…

Windows Server 2025開啟GPU分區(GPU-P)部署DoraCloud云桌面

本文描述在ShareStation工作站虛擬化方案的部署過程。 將服務器上部署 Windows Server、DoraCloud&#xff0c;并創建帶有vGPU的虛擬桌面。 GPU分區技術介紹 GPU-P&#xff08;GPU Partitioning&#xff09; 是微軟在 Windows 虛擬化平臺&#xff08;如 Hyper-V&#xff09;中…

Android RxJava框架分析:它的執行流程是如何的?它的線程是如何切換的?如何自定義RxJava操作符?

目錄 RxJava是什么&#xff1f;為什么使用。RxJava是如何使用的呢&#xff1f;RxJava如何和Retrofit一起使用。RxJava源碼分析。 &#xff08;1&#xff09;他執行流程是如何的。&#xff08;2&#xff09;map&#xff08;3&#xff09;線程的切換。 如何自定義RxJava操作符…

QT的初始代碼解讀及其布局和彈簧

this指的是真正的當前正在顯示的窗口 main函數&#xff1a; Widget w是生成了一個主窗口&#xff0c;QT Designer是在這個主窗口里塞組件 w.show()用來展示這個主窗口 頭文件&#xff1a; namespace Ui{class Widget;}中的class Widget和下面的class Widget不是一個東西 Ui…

什么是AI寫作

一、AI寫作簡介 AI 寫作正在成為未來 10 年最炙手可熱的超級技能。已經有越來越多的人通過 AI 寫作&#xff0c;在自媒體、公文寫作、商業策劃等領域實現了提效&#xff0c;甚至產生了變現收益。 掌握 AI 寫作技能&#xff0c;不僅能提高個人生產力&#xff0c;還可能在未來的 …

13.原生測試框架Unittest解決用例組織問題 與測試套件的使用

13. 原生測試框架Unittest解決用例組織問題 與測試套件的使用 一、測試架構核心組件解析 1.1 系統組成模塊 #mermaid-svg-bYie0B3MLRp0HL4g {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-bYie0B3MLRp0HL4g .erro…

UE5 腳部貼地不穿過地板方案

UE自帶的IK RIG和ControlRig技術 【UE5】角色腳部IK——如何讓腳貼在不同斜度的地面(設置腳的旋轉)_嗶哩嗶哩_bilibili 實驗后這個還是有一部分問題,首先只能保證高度不能穿過,但是腳步旋轉還是會導致穿模 IK前,整個模型在斜坡上會浮空 參考制作:https://www.youtube.com/w…

關于 js:4. 異步機制與事件循環

一、同步 vs 異步 1. 什么是同步&#xff08;Synchronous&#xff09; 同步代碼就是一行一行、按順序執行的。當前行沒有執行完&#xff0c;下一行不能動。 示例&#xff1a; console.log("A"); console.log("B"); console.log("C");輸出&am…