Java Notes 19 - Design Patterns - Creative Patterns

  • The purpose of using design patterns is to reusable code and improve code scalability and maintainability

  • Reuse code as much as possible to reduce code coupling

  • Design patterns are mainly refined based on OOP programming

  • Open-closed principle: Try to develop extensions and close them for modification. It is best to add code to complete new functions without modifying the code.

  • Liskov substitution principle: If we call the method of the parent class can be successful, replace it with the subclass call can also be successful

  • creative mode
  • Focus on how to create objects, the core is to separate the creation and use of objects, the two can be transformed relatively independently

factory method

Define an interface for creating objects and let subclasses decide which class to instantiate. The factory method is: defer creation of a class to subclasses

  • The purpose of the factory method: to make the creation of objects and the use of objects are separated, and the client always refers to the abstract factory and abstract product.
public class Main {
  public static void main(String[] args) {
    NumberFactory factory = NumberFactory.getFactory();
    Number result = factory.parse("1234");
    System.out.println(result);
  }
}

// Implement a Factory that parses strings to `Number`
interface NumberFactory {
  Number parse(String s);
  // Get factory instance
  static NumberFactory getFactory() {
    return impl;
  }
  static NumberFactory impl = new NumberFactoryImpl();
}

// Factory implementation class
class NumberFactoryImpl implements NumberFactory {
  public Number parse(String s) {
    return new BigDecimal(s);
  }
}
  • The real engineering NumberFactoryImpl and the product BigDecimal can be completely ignored
  • Allows independent changes to the code that creates the product without affecting the caller
interface NumberFactory {
  public static Number parse(String s) {
    return new BigDecimal(s);
  }
}
  • static factory method

  • The factory method can hide the details of creating a product, and will not actually create the product every time. It may also be taken from the cache, thereby improving speed and reducing memory consumption

  • For example, Integer.valueOf() can initialize many commonly used instances in advance, call the instance in the cache every time, and put another one in when there is none.

  • Always refer to the interface, can allow changes to subclasses without affecting the caller, object-oriented programming as much as possible

// TODO: I don't understand how List.of() executes and returns.
List<String> list = List.of("a", "b", "c", "d");
System.out.println(list);
class LocalDateFactory {
  private static Map<Integer, LocalDate> cache = new HashMap<Integer, LocalDate>();

  public static LocalDate fromIn(int yyyyMMdd) {
    if (yyyyMMdd > 20000101 && yyyyMMdd < 20200101) {
      if (cache.containsKey(yyyyMMdd)) return cache.get(yyyyMMdd);
      LocalDate value = createDate(yyyyMMdd);
      cache.put(yyyyMMdd, value);
      return value;
    } else {
      return createDate(yyyyMMdd);
    }
  }

  private static LocalDate createDate(int yyyyMMdd) {
    System.out.println("run createDate");
    return LocalDate.of(yyyyMMdd / 1000, yyyyMMdd / 100 % 100, yyyyMMdd % 100);
  }
}
  • The process of generating instances is unknown to the outside world, and appropriate caching can be performed to increase the efficiency of instance generation

abstract factory

Provides an interface for creating a series of related or interdependent objects without specifying their concrete classes

  • The factory is abstract, and the products produced by the factory are also abstract.
  • Then there is a specific factory 1, which realizes the factory, and the product 1-1 in factory 1 realizes the product
├── lib
└── src
    ├── App.java
    ├── fastfactory
    │   ├── FastFactory.java // actual factory
    │   ├── FastHtmlDocument.java // Actual product
    │   └── FastWordDocument.java // Actual product
    ├── goodfactory
    │   ├── GoodFactory.java // actual factory
    │   ├── GoodHtmlDocument.java // Actual product
    │   └── GoodWordDocument.java // Actual product
    └── services
        ├── AbstractFactory.java // virtual factory
        ├── HtmlDocument.java // Virtual products
        └── WordDocument.java // Virtual products
// virtual factory
public interface AbstractFactory {
  HtmlDocument createHtml(String md);

  WordDocument createWord(String md);
}
// Virtual products
public interface HtmlDocument {
  String toHtml();

  void save(Path path);
}
// Virtual products
public interface WordDocument {
  void save(Path path);
}
// actual factory
public class GoodFactory implements AbstractFactory {

  @Override
  public HtmlDocument createHtml(String md) {
    return new GoodHtmlDocument(md);
  }

  @Override
  public WordDocument createWord(String md) {
    return new GoodWordDocument(md);
  }

}
// Actual product
public class GoodHtmlDocument implements HtmlDocument {
  private String md;

  public GoodHtmlDocument(String md) {
    this.md = md;
  }

  @Override
  public String toHtml() {
    System.out.println(md);
    return null;
  }

  @Override
  public void save(Path path) {
  }

}
// Actual product
public class GoodWordDocument implements WordDocument {
  private String md;

  public GoodWordDocument(String md) {
    this.md = md;
  }

  @Override
  public void save(Path path) {
    System.out.println(md);
  }

}

Builder

Separating the construction and presentation of a complex object, allowing the same construction process to create different representations

public class HtmlBuilder {
  // Divide into multiple builder s, build step by step
  private HeadingBuilder headingBuilder = new HeadingBuilder();
  private HrBuilder hrBuilder = new HrBuilder();
  private ParagraphBuilder paragraphBuilder = new ParagraphBuilder();
  private QuoteBuilder quoteBuilder = new QuoteBuilder();

  public String toHtml(String md) {
    StringBuilder buffer = new StringBuilder();
    md.lines().forEach(line -> {
      if (line.startsWith("#")) {
        buffer.append(headingBuilder.buildHeading(line))
          .append("\n");
      } else if (line.startsWith(">")) {
        buffer.append(quoteBuilder.buildQuote(line))
          .append("\n");
      } else if (line.startsWith("---")) {
        buffer.append(hrBuilder.buildHr(line))
         .append("\n");
      } else {
        buffer.append(paragraphBuilder.buildParagraph(line))
          .append("\n");
      }
    });
    return buffer.toString();
  }
}
// Build in steps
public class URLBuilderImpl implements URLBuilder {

  private String domain = "www.baidu.com";
  private String scheme = "http";
  private String path = null;
  private Map<String, String> query;

  @Override
  public URLBuilder setDomain(String domain) {
    this.domain = Objects.requireNonNull(domain);
    return this;
  }

  @Override
  public URLBuilder setScheme(String scheme) {
    this.scheme = Objects.requireNonNull(scheme);
    return this;
  }

  @Override
  public URLBuilder setPath(String path) {
    this.path = Objects.requireNonNull(path);
    return this;
  }

  @Override
  public URLBuilder setQuery(Map<String, String> query) {
    this.query = query;
    return this;
  }

  @Override
  public String build() {
    StringBuilder sb =  new StringBuilder();
    sb.append(scheme)
      .append("://")
      .append(domain)
      .append(path);
    if (this.query.size() > 0) {
      sb.append("?");
      this.query.forEach((k, v) -> {
        sb.append(k).append("=").append(v).append("&");
      });
      sb.setLength(sb.length() - 1);
    }
    return sb.toString();
  }

  public URLBuilderImpl() {
  }
}

prototype

  • Use prototype instances to specify the kind of objects to create, and create new objects by copying these prototypes

Prototype mode is Prototype, which only refers to creating a new object based on an existing prototype.

public class Student  {
  private int id;
  private String name;
  private int score;

  // Copy the object and return the same type directly, omitting type conversion
  public Student copy() {
    Student std = new Student();
    std.id = this.id;
    std.name = this.name;
    std.score = this.score;
    return std;
  }
}

singleton

  • Guarantees only one instance of a class, and provides a global access point to it

The purpose of the singleton pattern is to ensure that in a process, there is exactly one instance of a class.

public class Singleton {
  // Static fields refer to unique instances:
  private static final Singleton INSTANCE = new Singleton();

  // Returning an instance from a static method
  public static Singleton getInstance() {
    return INSTANCE;
  }

  // private constructor, to ensure that the outside cannot be instantiated
  private Singleton() {
  }
}
public class Singleton {
  // Static fields refer to unique instances:
  public static final Singleton INSTANCE = new Singleton();

  // private constructor, to ensure that the outside cannot be instantiated
  private Singleton() {
  }
}
  • private privatization protection for constructors

  • The way of lazy loading, the way of multi-threading will cause errors

  • Implementing a singleton using an enumeration

    • Can avoid serialization and deserialization will bypass private methods of ordinary classes
    • You can also define your own field methods, etc.
// java guarantees that each enumeration of an enumeration class is a singleton
public enum World {
  // unique enumeration
  INSTANCE;

  private String name = "world";

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}
  • By convention, singletons are used directly in the framework. For example @Component

Tags: Java

Posted by Uranium-235 on Wed, 04 May 2022 05:54:43 +0300