Common design patterns

Keywords: Java Design Pattern Back-end

Create pattern

Singleton mode

Singleton Pattern is one of the simplest design patterns in Java.

This type of design pattern is a creation pattern, which provides the best way to create objects.

This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating the object of this class.

be careful

Intent: ensure that a class has only one instance and provide a global access point to access it.

Main solution: a globally used class is frequently created and destroyed.

When to use: when you want to control the number of instances and save system resources.

How to solve it: judge whether the system already has this single instance. If yes, it will be returned. If not, it will be created.

Key code: constructors are private.

Application example:

There is only one head teacher in a class.

Windows is multi process and multi-threaded. When operating a file, it is inevitable that multiple processes or threads operate a file at the same time, so all files must be processed through a unique instance.

Some device managers are often designed in singleton mode. For example, a computer has two printers, which must be processed when outputting. Two printers cannot print the same file.

advantage:

There is only one instance in memory, which reduces the memory overhead, especially the frequent creation and destruction of instances (such as the homepage page cache of the school of management).

Avoid multiple occupation of resources (such as writing files).

Disadvantages: there is no interface and cannot be inherited, which conflicts with the principle of single responsibility. A class should only care about the internal logic, not how to instantiate it outside.

Usage scenario:

A unique serial number is required for production.

The counters in the WEB do not need to be added to the database every time they are refreshed, but are cached first with a single instance.

Creating an object consumes too many resources, such as the connection between I/O and database.

Note: the getInstance() method needs to use the synchronization lock synchronized (Singleton.class) to prevent multiple threads from entering at the same time, causing the instance to be instantiated multiple times.

Implementation mode

Lazy, thread unsafe

Whether to initialize Lazy: Yes

Multithread safe: no

Implementation difficulty: easy

Description: This is the most basic implementation. The biggest problem with this implementation is that it does not support multithreading. Because there is no lock synchronized, it is not strictly a singleton mode.

This method of lazy loading obviously does not require thread safety and cannot work normally in multithreading.

public class Singleton { 
    private static Singleton instance; 
    private Singleton (){} 
    public static Singleton getInstance() { 
    if (instance == null) { 
        instance = new Singleton(); 
    } 
    return instance; 
    } 
}

Lazy, thread safe

Whether to initialize Lazy: Yes

Multithread safe: Yes

Implementation difficulty: easy

Description: this method has good lazy loading and can work well in multithreading. However, the efficiency is very low and synchronization is not required in 99% of cases.

Advantages: initialize only after the first call to avoid memory waste.

Disadvantages: you must lock synchronized to ensure single instance, but locking will affect efficiency.

The performance of getInstance() is not critical to the application (this method is used less frequently).

public class Singleton { 
    private static Singleton instance; 
    private Singleton (){} 
    public static synchronized Singleton getInstance() { 
    if (instance == null) { 
        instance = new Singleton(); 
    } 
    return instance; 
    } 
}

Hungry Han style

Lazy initialization: no

Multithread safe: Yes

Implementation difficulty: easy

Description: this method is commonly used, but it is easy to generate garbage objects.

Advantages: without locking, the execution efficiency will be improved.

Disadvantages: class initialization when loading, wasting memory.

It is based on the classloader mechanism to avoid the synchronization problem of multiple threads. However, instance is instantiated during class loading. Although there are many reasons for class loading, most of them call the getInstance method in the singleton mode, it is not sure that there are other ways (or other static methods) to cause class loading, At this time, initializing instance obviously does not achieve the effect of lazy loading.

public class Singleton { 
    private static Singleton instance = new Singleton();
    private Singleton (){} 
    public static Singleton getInstance() { 
    return instance; 
    } 
}

Double checked lock (DCL, double checked locking)

JDK version: from JDK1.5

Whether to initialize Lazy: Yes

Multithread safe: Yes

Implementation difficulty: relatively complex

Description: this method adopts double lock mechanism, which is safe and can maintain high performance in the case of multithreading.

The performance of getInstance() is critical to the application.

public class Singleton { 
    private volatile static Singleton singleton; 
    private Singleton (){} 
    public static Singleton getSingleton() { 
    if (singleton == null) { 
        synchronized (Singleton.class) { 
            if (singleton == null) { 
                singleton = new Singleton(); 
            } 
        } 
    } 
    return singleton; 
    } 
}

Registered / static inner class

Whether to initialize Lazy: Yes

Multithread safe: Yes

Implementation difficulty: General

Description: this method can achieve the same effect as the double check lock method, but the implementation is simpler. When using deferred initialization for static domains, this should be used instead of double check locking. This method is only applicable to the static domain. The double check lock method can be used when the instance domain needs to delay initialization.

This method also uses the classloader mechanism to ensure that there is only one thread when initializing an instance. It is different from the third method: in the third method, as long as the Singleton class is loaded, the instance will be instantiated (the lazy loading effect is not achieved). This method is that the Singleton class is loaded, and the instance is not necessarily initialized. Because the SingletonHolder class is not actively used, the SingletonHolder class will be explicitly loaded to instantiate the instance only when the getInstance method is explicitly called. Imagine if instantiating an instance consumes a lot of resources, so you want it to delay loading. On the other hand, you don't want to instantiate it when the Singleton class is loaded, because you can't ensure that the Singleton class may be actively used and loaded in other places. It's obviously inappropriate to instantiate an instance at this time. At this time, this method is very reasonable compared with the third method.

public class Singleton { 
    private static class SingletonHolder { 
    private static final Singleton INSTANCE = new Singleton(); 
    } 
    private Singleton (){} 
    public static final Singleton getInstance() { 
    return SingletonHolder.INSTANCE; 
    } 
}

enumeration

JDK version: from JDK1.5

Lazy initialization: no

Multithread safe: Yes

Implementation difficulty: easy

Description: this implementation has not been widely used, but it is the best way to implement singleton mode. It is more concise, automatically supports serialization mechanism and absolutely prevents multiple instantiations.

This method is advocated by Effective Java author Josh Bloch. It can not only avoid the problem of multi-threaded synchronization, but also automatically support the serialization mechanism to prevent deserialization, re create new objects, and absolutely prevent multiple instantiations. However, since the enum feature was added after JDK1.5, it is strange to write in this way, and it is rarely used in practical work.

Private constructor cannot be called through reflection attack.

public enum Singleton { 
    INSTANCE; 
    public void whateverMethod() { 
    } 
}

Experience: generally, the first and second lazy ways are not recommended, and the third hungry way is recommended. The fifth registration method is only used when the lazy loading effect is to be clearly realized. If it involves deserialization to create objects, you can try the sixth enumeration method. If there are other special requirements, the fourth double check lock mode can be considered.

Factory mode

Factory Pattern is one of the most commonly used design patterns in Java.

This type of design pattern is a creation pattern, which provides the best way to create objects.

In factory mode, when creating objects, we do not expose the creation logic to the client, and point to the newly created objects by using a common interface.

be careful

Intention: define an interface for creating objects, and let its subclasses decide which factory class to instantiate. The factory pattern delays the creation process to the subclasses.

Main solution: mainly solve the problem of interface selection.

Advantages: 1. If a caller wants to create an object, he just needs to know its name. 2. High scalability. If you want to add a product, you can only extend a factory class. 3. Mask the specific implementation of the product, and the caller only cares about the product interface.

Disadvantages: each time you add a product, you need to add a specific class and object implementation factory, which multiplies the number of classes in the system, increases the complexity of the system to a certain extent, and also increases the dependence of the specific classes of the system. This is not a good thing.

Implementation method

Create an interface:

Shape.java
public interface Shape {
   void draw();
}

Create an entity class that implements the interface

Rectangle.java
public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}
Square.java
public class Square implements Shape {
    @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}
Circle.java
public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

Create a factory to generate objects of entity classes based on the given information

ShapeFactory.java
public class ShapeFactory {
   //Use the getShape method to get an object of shape type
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }       
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
}

Using this factory, the object of the entity class is obtained by passing type information

FactoryPatternDemo.java
public class FactoryPatternDemo {
   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();
      //Get the object of Circle and call its draw method
      Shape shape1 = shapeFactory.getShape("CIRCLE");
      //Call Circle's draw method
      shape1.draw();
      //Get the object of Rectangle and call its draw method
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
      //Call the draw method of Rectangle
      shape2.draw();
      //Get the Square object and call its draw method
      Shape shape3 = shapeFactory.getShape("SQUARE");
      //Call Square's draw method
      shape3.draw();
   }
}

Execute the program and output the results:

Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.

Behavioral model

Responsibility chain model

The Chain of Responsibility Pattern creates a chain of receiver objects for requests.

This mode gives the type of request and decouples the sender and receiver of the request. This type of design pattern belongs to behavioral pattern.

In this pattern, each recipient usually contains a reference to another recipient. If an object cannot process the request, it passes the same request to the next recipient, and so on.

be careful

Intention: avoid coupling the sender and receiver of the request, make it possible for multiple objects to receive the request, connect these objects into a chain, and pass the request along the chain until an object processes it.

Main solution: the handler in the responsibility chain is responsible for processing the request. The customer only needs to send the request to the responsibility chain without paying attention to the processing details of the request and the transmission of the request. Therefore, the responsibility chain decouples the sender of the request from the handler of the request.

When to use: filter many channels when processing messages.

How to solve this problem: all intercepted classes implement a unified interface.

Key code: the Handler aggregates itself and determines whether it is appropriate in the HandlerRequest. If the conditions are not met, it will be passed down and set before passing to whom.

Application examples: 1. The "beating drums and passing flowers" in the dream of Red Mansions. 2. Event bubbling in JS. 3. Encoding processing by Apache Tomcat in JAVA WEB, interceptor of struts 2 and Filter of jsp servlet.

Advantages: 1. Reduce the coupling degree. It decouples the sender and receiver of the request. 2. Simplifies objects. So that the object does not need to know the structure of the chain. 3. Enhance the flexibility of assigning responsibilities to objects. By changing the members in the chain or transferring their order, it is allowed to dynamically add or delete responsibilities. 4. It is convenient to add new request processing classes.

Disadvantages: 1. There is no guarantee that the request will be received. 2. The system performance will be affected to some extent, and it is inconvenient to debug the code, which may cause circular calls. 3. It may not be easy to observe the characteristics of the runtime, which hinders debugging.

Usage scenario: 1. Multiple objects can process the same request. The specific object to process the request is automatically determined by the runtime. 2. Submit a request to one of multiple objects without explicitly specifying the recipient. 3. You can dynamically specify a set of objects to handle requests.

Implementation method

Create an abstract logger class.

AbstractLogger.java
public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;
   protected int level;
   //The next element in the chain of responsibility
   protected AbstractLogger nextLogger;
   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }
   abstract protected void write(String message);
}

Create an entity class that extends the logger class

ConsoleLogger.java
public class ConsoleLogger extends AbstractLogger {
   public ConsoleLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {   
      System.out.println("Standard Console::Logger: " + message);
   }
}
ErrorLogger.java
public class ErrorLogger extends AbstractLogger {
   public ErrorLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {   
      System.out.println("Error Console::Logger: " + message);
   }
}
FileLogger.java
public class FileLogger extends AbstractLogger {
   public FileLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {   
      System.out.println("File::Logger: " + message);
   }
}

Create different types of recorders. Give them different error levels and set the next recorder in each recorder. The next recorder in each recorder represents part of the chain.

ChainPatternDemo.java
public class ChainPatternDemo {
   private static AbstractLogger getChainOfLoggers(){
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
      return errorLogger; 
   }
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
      loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
      loggerChain.logMessage(AbstractLogger.DEBUG,"This is a debug level information.");
      loggerChain.logMessage(AbstractLogger.ERROR,"This is an error information.");
   }
}

Execute the program and output the results:

​Standard Console::Logger: This is an information.
File::Logger: This is a debug level information.
Standard Console::Logger: This is a debug level information.
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.​

Structural model

Adapter mode

The Adapter Pattern serves as a bridge between two incompatible interfaces.

This type of design pattern belongs to structural pattern, which combines the functions of two independent interfaces.

This pattern involves a single class that is responsible for adding independent or incompatible interface functions. For a real example, the card reader is used as an adapter between the memory card and the notebook. You insert the memory card into the card reader, and then insert the card reader into the notebook, so that you can read the memory card through the notebook.

be careful

Intent: convert the interface of a class into another interface that the customer wants. The adapter pattern allows classes that cannot work together because of interface incompatibility to work together.

Main solution: it mainly solves that in software systems, some "existing objects" often need to be put into a new environment, and the interfaces required by the new environment cannot be met by the existing objects.

When to use: 1. The system needs to use existing classes, and such interfaces do not meet the needs of the system. 2. You want to create a reusable class to work with some classes that are not closely related to each other, including some classes that may be introduced in the future. These source classes do not necessarily have consistent interfaces. 3. Insert one class into another class family through interface conversion. (for example, tigers and birds, now there is a flying tiger. Without increasing the requirements of entities, an adapter is added to contain a tiger object and realize the flying interface.)

How to solve: inheritance or dependency (recommended).

Key code: the adapter inherits or relies on existing objects to implement the desired target interface.

Application examples: 1. American electric appliance is 110V and China is 220V. There must be an adapter to convert 110V into 220V. 2. JAVA JDK 1.1 provides the Enumeration interface, while 1.2 provides the Iterator interface. If you want to use the 1.2 JDK, you need to convert the Enumeration interface of the previous system into the Iterator interface. At this time, you need the adapter mode. 3. Run WINDOWS program on LINUX. 4. jdbc in JAVA.

Advantages: 1. It can let any two classes that are not associated run together. 2. Class reuse is improved. 3. Increased class transparency. 4. Good flexibility.

Disadvantages: 1. Excessive use of adapters will make the system very messy and difficult to grasp as A whole. For example, it is obvious that the A interface is called, but in fact, it is internally adapted to the implementation of B interface. If this happens too often in A system, it is tantamount to A disaster. Therefore, if it is not necessary, you can refactor the system directly instead of using adapters. 2. Since JAVA inherits at most one class, at most one adapter class can be adapted, and the target class must be an abstract class.

Usage scenario: if you are motivated to modify the interface of a normal system, you should consider using the adapter mode.

Note: adapters are not added during detailed design, but solve the problems of projects in service.

Implementation method

Create interfaces for media players and more advanced media players

MediaPlayer.java
public interface MediaPlayer {
   public void play(String audioType, String fileName);
}
AdvancedMediaPlayer.java
public interface AdvancedMediaPlayer {
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}

Create an entity class that implements the AdvancedMediaPlayer interface.

VlcPlayer.java
public class VlcPlayer implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      System.out.println("Playing vlc file. Name: "+ fileName);     
   }
   @Override
   public void playMp4(String fileName) {
      //Do nothing
   }
}
Mp4Player.java
public class Mp4Player implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      //Do nothing
   }
   @Override
   public void playMp4(String fileName) {
      System.out.println("Playing mp4 file. Name: "+ fileName);     
   }
}

Create an adapter class that implements the MediaPlayer interface.

MediaAdapter.java
public class MediaAdapter implements MediaPlayer {
   AdvancedMediaPlayer advancedMusicPlayer;
   public MediaAdapter(String audioType){
      if(audioType.equalsIgnoreCase("vlc") ){
         advancedMusicPlayer = new VlcPlayer();      
      } else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      } 
   }
   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      }else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}

Create an entity class that implements the MediaPlayer interface.

AudioPlayer.java
public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter;
   @Override
   public void play(String audioType, String fileName) {   
      //Built in support for playing mp3 music files
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: "+ fileName);        
      }
      //mediaAdapter provides support for playing other file formats
      else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      else{
         System.out.println("Invalid media. "+ audioType + " format not supported");
      }
   }  
}

Use AudioPlayer to play different types of audio formats.

AdapterPatternDemo.java
public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();
      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}

Execute the program and output the results:

Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

proxy pattern

In Proxy Pattern, one class represents the functions of another class.

This type of design pattern belongs to structural pattern.

In proxy mode, we create objects with existing objects to provide functional interfaces to the outside world.

be careful

Intent: to provide a proxy for other objects to control access to this object.

It mainly solves the problems caused by directly accessing objects. For example, the object to be accessed is on a remote machine. In an object-oriented system, direct access to some objects will bring a lot of trouble to users or system structure for some reasons (such as high object creation cost, or some operations need security control, or need out of process access). We can add an access layer to this object when accessing this object.

When to use: I want to do some control when accessing a class.

How to solve: add an intermediate layer.

Key code: combination of implementation and proxy class.

Application examples: 1. Shortcuts in Windows. 2. Pig Bajie went to Gao Cuilan, and the result was that the monkey king changed. It can be understood as follows: Abstract Gao Cuilan's appearance. Gao Cuilan himself and the monkey king have implemented this interface. When pig Bajie visited Gao Cuilan, he can't see that this is the monkey king, so it is said that the monkey king is the agent class of Gao Cuilan. 3. To buy a train ticket, you don't have to buy it at the railway station. You can also go to the selling point. 4. A check or certificate of deposit is an agent for the funds in an account. Cheques are used to replace cash in market transactions and provide control over the funds on the issuer's account number. 5,spring aop.

Advantages: 1. Clear responsibilities. 2. High scalability. 3. Intelligent.

Disadvantages: 1. Due to the addition of proxy objects between the client and the real topic, some types of proxy modes may slow down the processing speed of requests. 2. Implementing the proxy pattern requires additional work, and the implementation of some proxy patterns is very complex.

Usage scenarios: divided by responsibilities, there are usually the following usage scenarios: 1. Remote agent. 2. Virtual agent. 3. Copy on write agent. 4. Protect or Access agent. 5. Cache agent. 6. Firewall agent. 7. Synchronization agent. 8. Smart Reference proxy.

Note: 1. Difference between adapter mode and adapter mode: adapter mode mainly changes the interface of the considered object, while proxy mode cannot change the interface of the proxy class. 2. The difference between decorator mode and decorator mode: decorator mode is for enhancement, while agent mode is for control.

Implementation method

Create an interface

Image.java
public interface Image {
   void display();
}

Create an entity class that implements the interface

RealImage.java
public class RealImage implements Image {
   private String fileName;
   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }
   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   } 
   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}
ProxyImage.java
public class ProxyImage implements Image{
   private RealImage realImage;
   private String fileName;
   public ProxyImage(String fileName){
      this.fileName = fileName;
   }
   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}

When requested, use ProxyImage to get the object of RealImage class

ProxyPatternDemo.java
public class ProxyPatternDemo {
   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");
      // The image will be loaded from disk
      image.display();
      System.out.println("");
      // Images do not need to be loaded from disk
      image.display(); 
   }
}

Execute the program and output the results:

Loading test_10mb.jpg
Displaying test_10mb.jpg
Displaying test_10mb.jpg

Appearance mode

Facade Pattern hides the complexity of the system and provides an interface for the client to access the system.

This type of design pattern belongs to structural pattern. It adds an interface to the existing system to hide the complexity of the system.

This pattern involves a single class that provides simplified methods for client requests and delegate calls to existing system class methods.

be careful

Intention: provide a consistent interface for a group of interfaces in the subsystem. The appearance pattern defines a high-level interface, which makes the subsystem easier to use.

The main solution is to reduce the complexity of accessing the internal subsystems of the complex system and simplify the interface between clients.

When to use: 1. The client does not need to know the complex connections within the system. The whole system only needs to provide a "receptionist". 2. Define the entry of the system.

How to solve it: the client is not coupled with the system, and the appearance class is coupled with the system.

Key code: add another layer between the client and the complex system, which handles the call sequence and dependencies.

Application examples: 1. When you go to a hospital to see a doctor, you may have to go to registration, outpatient service, price setting and medicine taking, which makes the patient or his family feel very complicated. If there is a receptionist, it is very convenient to let the receptionist handle it only. 2. JAVA three-tier development model.

Advantages: 1. Reduce system interdependence. 2. Increase flexibility. 3. Improved security.

Disadvantages: it does not comply with the opening and closing principle. If it is troublesome to change things, inheritance and rewriting are inappropriate.

Usage scenarios: 1. Modules that provide external access to complex modules or subsystems. 2. The subsystem is relatively independent. 3. Prevent risks caused by low-level personnel.

Note: in the hierarchical structure, appearance patterns can be used to define the entry of each layer in the system.

Implementation method

Create an interface

Shape.java
public interface Shape {
   void draw();
}

Create an entity class that implements the interface.

Rectangle.java
public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}
Square.java
public class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Square::draw()");
   }
}
Circle.java
public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}

Create an appearance class

ShapeMaker.java
public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;
   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }
   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

Use this appearance class to draw various types of shapes

FacadePatternDemo.java
public class FacadePatternDemo {
   public static void main(String[] args) {
      ShapeMaker shapeMaker = new ShapeMaker();
      shapeMaker.drawCircle();
      shapeMaker.drawRectangle();
      shapeMaker.drawSquare();     
   }
}

Execute the program and output the results:

Circle::draw()
Rectangle::draw()
Square::draw()

Posted by ijmccoy on Thu, 21 Oct 2021 20:54:02 -0700