当前位置: 首页 > news >正文

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.xdnf.cn/news/355609.html

相关文章:

  • 数据库故障排查指南
  • React+Taro选择日期组件封装
  • 51c自动驾驶~合集40
  • 新品:同等小体积通信距离翻一倍-RF3060F27通信模块
  • Vmware 最新下载教程和安装教程,外带免下载文件
  • project从入门到精通(四)
  • idea spring boot 打包成可执行的 JAR包
  • 使用docker安装Dinky
  • `timescale 1ns/1ps的意义
  • 【250GB空间不够用】
  • 发那科机器人4(编程实例)
  • [Unity]-[UI]-[Image] 关于UI精灵图资源导入设置的详细解释
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(17):「 」と言いました
  • 芋道框架 账号未登录、租户标识未传递
  • 云效 MCP Server:AI 驱动的研发协作新范式
  • # YOLOv2:目标检测的升级之作
  • 课程审核流程揭秘:确保内容合规与用户体验
  • 五、【LLaMA-Factory实战】模型部署与监控:从实验室到生产的全链路实践
  • C++跨平台开发实践:深入解析与常见问题处理指南
  • 在线服务器具体是指什么?
  • <uniapp><HBuilder><故障>HBuilder真机运行时,报“同步资源失败”故障解决
  • 使用AES-CBC + HMAC-SHA256实现前后端请求安全验证
  • Excel实现单元格内容拼接
  • 《探索React Native社交应用中WebRTC实现低延迟音视频通话的奥秘》
  • Linux 一键部署chrony时间服务器
  • Debezium RelationalSnapshotChangeEventSource详解
  • OpenCV 中用于支持 华为昇腾(Ascend)AI 芯片后端 的模块CANN
  • [数据库][sqlserver]查看索引碎片
  • Docker网络模式深度解析:Bridge与Host模式对比及实践指南
  • 华为银河麒麟 V10(ARM)系统软件部署全攻略:Redis、RabbitMQ、MySQL 等集群搭建指南