Java 23种设计模式 - 结构型模式7种

news2025/5/10 13:59:46

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 {

    @Override
    public 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;
    }
    
    @Override
    public void cookDinner() {
        System.out.println("先洗手");
        cook.cookDinner();
    }
    
}
public class WashHearCook extends FilterCook {
    
    public WashHearCook(Cook cook) {
        this.cook = cook;
    }
    
    @Override
    public 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() {
                    @Override
                    public 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();

    }

    @Override
    public 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 {

    @Test
    public 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;
   }
 
   @Override
   public 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.coloradmin.cn/o/2372354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库(MySQL)基础

一、登录数据库 在linux系统中登录数据库的指令 mysql -h 127.48.0.236 -P 3306 -u root -p -h&#xff1a;填写IP地址&#xff0c;指明要连接的主机。如果不加该字段表示本地主机-P&#xff1a;填写端口号&#xff0c;指明进程。 如果不加该字段会使用默认的端口号。-u&…

Vue 2.0 详解全教程(含 Axios 封装 + 路由守卫 + 实战进阶)

目录 一、Vue 2.0 简介1.1 什么是 Vue&#xff1f;1.2 Vue 2.x 的主要特性 二、快速上手2.1 引入 Vue2.2 创建第一个 Vue 实例 三、核心概念详解3.1 模板语法3.2 数据绑定3.3 事件绑定3.4 计算属性 & 侦听器 四、组件系统4.1 定义全局组件4.2 单文件组件&#xff08;*.vue …

依赖关系-根据依赖关系求候选码

关系模式R&#xff08;U, F&#xff09;, U{}&#xff0c;F是R的函数依赖集&#xff0c;可以将属性分为4类&#xff1a; L: 仅出现在依赖集F左侧的属性 R: 仅出现在依赖集F右侧的属性 LR: 在依赖集F左右侧都出现的属性 NLR: 在依赖集F左右侧都未出现的属性 结论1: 若X是L类…

uniapp-商城-47-后台 分类数据的生成(通过数据)

在第46章节中&#xff0c;我们为后台数据创建了分类的数据表结构schema&#xff0c;使得可以通过后台添加数据并保存&#xff0c;同时使用云函数进行数据库数据的读取。文章详细介绍了如何通过前端代码实现分类管理功能&#xff0c;包括获取数据、添加、更新和删除分类。主要代…

java-----------------多态

多态&#xff0c;当前指的是 java 所呈现出来的一个对象 多态 定义 多态是指同一个行为具有多个不同表现形式或形态的能力。在面向对象编程中&#xff0c;多态通过方法重载和方法重写来实现。 强弱类型语言 javascript 或者python 是弱类型语言 C 语言&#xff0c;或者 C…

【文档智能】开源的阅读顺序(Layoutreader)模型使用指南

一年前&#xff0c;笔者基于开源了一个阅读顺序模型&#xff08;《【文档智能】符合人类阅读顺序的文档模型-LayoutReader及非官方权重开源》&#xff09;&#xff0c; PDF解析并结构化技术路线方案及思路&#xff0c;文档智能专栏 阅读顺序检测旨在捕获人类读者能够自然理解的…

Edu教育邮箱申请2025年5月

各位好&#xff0c;这里是aigc创意人竹相左边 如你所见&#xff0c;这里是第3部分 现在是选择大学的学科专业 选专业的时候记得考虑一下当前的时间日期。 比如现在是夏天&#xff0c;所以你选秋天入学是合理的。

STM32-TIM定时中断(6)

目录 一、TIM介绍 1、TIM简介 2、定时器类型 3、基本定时器 4、通用定时器 5、定时中断基本结构 6、时基单元的时序 &#xff08;1&#xff09;预分频器时序 &#xff08;2&#xff09;计数器时序 7、RCC时钟树 二、定时器输出比较功能&#xff08;PWM&#xff09; …

Modbus RTU 详解 + FreeMODBUS移植(附项目源码)

文章目录 前言一、Modbus RTU1.1 通信方式1.2 模式特点1.3 数据模型1.4 常用功能码说明1.5 异常响应码1.6 通信帧格式1.6.1 示例一&#xff1a;读取保持寄存器&#xff08;功能码 0x03&#xff09;1.6.2 示例二&#xff1a;写单个线圈&#xff08;功能码 0x05&#xff09;1.6.3…

对称加密算法(AES、ChaCha20和SM4)Python实现——密码学基础(Python出现No module named “Crypto” 解决方案)

文章目录 一、对称加密算法基础1.1 对称加密算法的基本原理1.2 对称加密的主要工作模式 二、AES加密算法详解2.1 AES基本介绍2.2 AES加密过程2.3 Python中实现AES加密Python出现No module named “Crypto” 解决方案 2.4 AES的安全考量 三、ChaCha20加密算法3.1 ChaCha20基本介…

【软件设计师:存储】16.计算机存储系统

一、主存储器 存储器是计算机系统中的记忆设备,用来存放程序和数据。 计算机中全部信息,包括输入的原始数据、计算机程序、中间运 行结果和最终运行结果都保存在存储器中。 存储器分为: 寄存器Cache(高速缓冲存储器)主存储器辅存储器一、存储器的存取方式 二、存储器的性…

WebRTC通信原理与流程

1、服务器与协议相关 1.1 STUN服务器 图1.1.1 STUN服务器在通信中的位置图 1.1.1 STUN服务简介 STUN&#xff08;Session Traversal Utilities for NAT&#xff0c;NAT会话穿越应用程序&#xff09;是一种网络协议&#xff0c;它允许位于NAT&#xff08;或多重 NAT&#xff09;…

Java版ERP管理系统源码(springboot+VUE+Uniapp)

ERP系统是企业资源计划&#xff08;Enterprise Resource Planning&#xff09;系统的缩写&#xff0c;它是一种集成的软件解决方案&#xff0c;用于协调和管理企业内各种关键业务流程和功能&#xff0c;如财务、供应链、生产、人力资源等。它的目标是帮助企业实现资源的高效利用…

Redis总结(六)redis持久化

本文将简单介绍redis持久化的两种方式 redis提供了两种不同级别的持久化方式&#xff1a; RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保…

PMIC电源管理模块的PCB设计

目录 PMU模块简介 PMU的PCB设计 PMU模块简介 PMIC&#xff08;电源管理集成电路&#xff09;是现代电子设备的核心模块&#xff0c;负责高效协调多路电源的转换、分配与监控。它通过集成DC-DC降压/升压、LDO线性稳压、电池充电管理、功耗状态切换等功能&#xff0c;替代传统分…

华为云Flexus+DeepSeek征文|DeepSeek-V3商用服务开通教程

目录 DeepSeek-V3/R1商用服务开通使用感受 DeepSeek-V3/R1商用服务开通 1、首先需要访问ModelArts Studio_MaaS_大模型即服务_华为云 2、在网站右上角登陆自己的华为云账号&#xff0c;如果没有华为云账号的话&#xff0c;则需要自己先注册一个。 3、接着点击ModelArts Stu…

Qt—鼠标移动事件的趣味小程序:会移动的按钮

1.项目目标 本次根据Qt的鼠标移动事件实现一个趣味小程序&#xff1a;当鼠标移动到按钮时&#xff0c;按钮就会随机出现在置&#xff0c;以至于根本点击不到按钮。​​​​​ 2.项目步骤 首先现在ui界面设计控件(也可以用代码的方式创建&#xff0c;就不多说了) 第一个按钮不需…

鞋样设计软件

Sxy 64鞋样设计软件是一款专业级鞋类设计工具 专为鞋业设计师与制鞋企业开发 该软件提供全面的鞋样设计功能 包括二维开版 三维建模 放码排料等核心模块 支持从草图构思到成品输出的完整设计流程 内置丰富的鞋型数据库与部件库 可快速生成各种鞋款模板 软件采用智能放码技术 精…

LeRobot 项目部署运行逻辑(六)——visualize_dataset_html.py/visualize_dataset.py

可视化脚本包括了两个方法&#xff1a;远程下载 huggingface 上的数据集和使用本地数据集 脚本主要使用两个&#xff1a; 目前来说&#xff0c;ACT 采集训练用的是统一时间长度的数据集&#xff0c;此外&#xff0c;这两个脚本最大的问题在于不能裁剪&#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;中…