Interview notes - 4 Creative Design Patterns

Posted by Blicka on Fri, 21 Jan 2022 05:56:17 +0100

1) Singleton mode

Usage scenario:

1.1) handling resource access conflicts

For example, if multiple classes log to a file at the same time, the content written may be overwritten in the case of multithreading

1.2) represents a globally unique class

For example, configure information classes. Another example is the unique incremental ID number generator

The code of the classic [double detection] implementation method is as follows:

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

Static internal class, implementation code is as follows:

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

SingletonHolder is a static internal class. When the external class IdGenerator is loaded, the SingletonHolder instance object will not be created. SingletonHolder will be loaded only when getInstance() method is called, and instance will be created at this time. The uniqueness of instance and the thread safety of the creation process are guaranteed by the JVM. Therefore, this implementation method can not only ensure thread safety, but also delay loading

2) Factory mode

Code before improvement:

public class RuleConfigParserFactory {
  public static IRuleConfigParser createParser(String configFormat) {
    IRuleConfigParser parser = null;
    if ("json".equalsIgnoreCase(configFormat)) {
      parser = new JsonRuleConfigParser();
    } else if ("xml".equalsIgnoreCase(configFormat)) {
      parser = new XmlRuleConfigParser();
    } else if ("yaml".equalsIgnoreCase(configFormat)) {
      parser = new YamlRuleConfigParser();
    } else if ("properties".equalsIgnoreCase(configFormat)) {
      parser = new PropertiesRuleConfigParser();
    }
    return parser;
  }
}

Improvement Code:

public interface IRuleConfigParserFactory {
  IRuleConfigParser createParser();
}
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new JsonRuleConfigParser();
  }
}
public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new XmlRuleConfigParser();
  }
}
public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new YamlRuleConfigParser();
  }
}
public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new PropertiesRuleConfigParser();
  }
}

use:

public class RuleConfigParserFactoryMap {
  private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();
  static {
    cachedFactories.put("json", new JsonRuleConfigParserFactory());
    cachedFactories.put("xml", new XmlRuleConfigParserFactory());
    cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
    cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
  }
  public static IRuleConfigParserFactory getParserFactory(String type) {
    if (type == null || type.isEmpty()) {
      return null;
    }
    IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
    return parserFactory;
  }
}

3) Builder mode

The code before transformation is as follows:

public class ResourcePoolConfig {
  private String name;
  private int maxTotal = 8;
  private int minIdle = 0;
  
  public ResourcePoolConfig(String name) { this.name = name;}

  public void setMaxTotal(int maxTotal) {
    if (maxTotal <= 0) {
      throw new IllegalArgumentException("maxTotal should be positive.");
    }
    this.maxTotal = maxTotal;
  }
  //... Omit getter method
}

use:

ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool");
config.setMaxTotal(26);

Change to builder mode, and the code is as follows:

public class ResourcePoolConfig {
  private String name;
  private int maxTotal;

  private ResourcePoolConfig(Builder builder) {
    this.name = builder.name;
    this.maxTotal = builder.maxTotal;
  }
  //... Omit getter method

  //We designed the Builder class as the internal class of ResourcePoolConfig.
  public static class Builder {

    private String name;
    private int maxTotal = 8;

    public ResourcePoolConfig build() {
      // Verification logic is put here, including required item verification, dependency verification, constraint verification, etc
      if (StringUtils.isBlank(name)) {
        throw new IllegalArgumentException("...");
      }
      return new ResourcePoolConfig(this);
    }

    public Builder setName(String name) {
      if (StringUtils.isBlank(name)) {
        throw new IllegalArgumentException("...");
      }
      this.name = name;
      return this;
    }

    public Builder setMaxTotal(int maxTotal) {
      if (maxTotal <= 0) {
        throw new IllegalArgumentException("...");
      }
      this.maxTotal = maxTotal;
      return this;
    }

  }
}

use:

ResourcePoolConfig config = new ResourcePoolConfig.Builder()
        .setName("dbconnectionpool")
        .setMaxTotal(16)
        .build();

Difference between factory mode and builder mode:

Factory mode is used to create objects of different but related types (a group of subclasses inheriting the same parent class or interface). The given parameters determine which type of object to create. Builder mode is used to create a type of complex object. Different objects are created "customized" by setting different optional parameters.

Popular example explanation: when customers enter a restaurant to order, we use the factory mode to make different foods according to different choices of users, such as pizza, hamburger and salad. For pizza, users can customize various ingredients, such as cheese, tomato and cheese. We make pizza according to different ingredients selected by users through the builder mode.

4) Prototype mode

If the cost of creating objects is relatively large, and there is little difference between different objects of the same class (most fields are the same), in this case, we can create new objects by copying (or copying) existing objects (prototypes), so as to save creation time. This method of creating objects based on prototypes is called Prototype Design Pattern, which is called prototype pattern for short

4.1) it is implemented in shallow copy mode, and the code is as follows:

class Realizetype implements Cloneable {
    Realizetype() {
        System.out.println("Prototype created successfully!");
    }
    public Object clone() throws CloneNotSupportedException {
        System.out.println("Prototype copied successfully!");
        return (Realizetype) super.clone();
    }
}
//Prototype mode testing
public class PrototypeTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Realizetype obj1 = new Realizetype();
        Realizetype obj2 = (Realizetype) obj1.clone();
        System.out.println("obj1==obj2?" + (obj1 == obj2));
    }
}

4.2) deep copy implementation, the code is as follows:

The object is serialized and then deserialized into a new object

//Write object to stream
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
//Remove from stream
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return (YuelyLog)objectInputStream.readObject();

Final summary:

Summary: Singleton mode is used to create globally unique objects. Factory mode is used to create objects of different but related types (a group of subclasses inheriting the same parent class or interface). The given parameters determine which type of object to create. Builder mode is used to create complex objects. Different objects can be "customized" by setting different optional parameters. Prototype mode creates objects with high creation cost by copying existing objects, so as to save creation time

Topics: Design Pattern