Start daily boasting
Hi, everyone. I’m cool and cool. Long time no see. I’m sorry for the slow update of the article due to my busy schedule recently. 😊 Today we continue our discussion of design patterns. I’ve talked about a few patterns, so if you haven’t read them, you can review them.
Review 👉 handled in the original 】 【 let design patterns to fly for a while | (1) the opening 👉 original 】 【 let design patterns to fly for a while | (2) the singleton pattern 👉 original 】 【 let design pattern fly | 3. The factory pattern for a while
👉 【 original 】 let design patterns to fly for a while | prototype model
So, today we’re going to talk about Builder mode, which is the last of GOF23’s builder modes. Since it is a creation pattern, it is also a pattern for creating objects, as the name of the pattern suggests.
Scene demand
Builder, that’s what you do to create objects. This sounds like nonsense……
Aren’t objects built? How is this a separate pattern? And what’s the difference between those patterns?
In order to facilitate understanding, I still do not apply some theories from books or online, but I have learned some understanding, combined with some scenes of life to tell you.
For example, xiao Ming went to the computer city to assemble a computer. A complete computer will certainly contain some accessories, such as monitor, keyboard, mouse and so on, and the configuration parameters of each accessory are hundreds and thousands, and there will be some professional parameters on each hardware that we do not understand. However, all we need to buy is a computer, we just need to tell the computer seller, we need to buy what brand, what grade, what price of computer or accessories, so that the computer merchant will give us a good assembly of a more suitable computer in all aspects. Xiao Ming usually simply write a document to watch a video what, the configuration requirements are not high, so only need entry level computer can. The diagram below:
Then Xiao Ming played a few years after the university, inadvertently infected with the problem of playing games, spiralling out of control, the original computer configuration has card out of the sky. So, he wants to change the game book again, as shown below:
Then xiaoming university graduated, because the university every day to play games, after graduation is really mixed not go down, I heard that learning Java technology salary is high, but also can install force, so no words to switch to learning Java…… But learn Java to use the original broken computer can not line, how also use a MacBook Pro to deserve the program ape on this lofty position, so……
This is a very common scenario in daily life, how to use code to achieve it?
If you haven’t learned any design patterns before, you might design like this (pseudocode) :
Public class Computer {memory CPU disk mouse... } public class Client{ voidbuy(){// Computer comp = new Computer(); Comp. Set memory ("4g");
comp.setCPU("i3"); Comp. Set the hard disk ("128g"); Comp. Set the mouse ("Cow"); . } deliver the computer to the customer... }Copy the code
There is nothing wrong with writing like this, which is how we write most of the time.
So think about it, what’s the problem with writing this?
Now xiao Ming’s computer is going to upgrade to the game book, this time trouble comes, he has to modify the above buy() method inside the code, the original
Comp. Set memory ("4g");
comp.setCPU("i3"); Comp. Set the hard disk ("128g"); Comp. Set the mouse ("Cow");
Copy the code
Modify to new configuration:
Comp. Set memory ("8g");
comp.setCPU("i5"); Comp. Set the hard disk ("256g"); Comp. Set the mouse (Logitech);
Copy the code
The same goes for upgrading to a MacBook Pro. I also need to change the code if I need to add or subtract some configuration.
Obviously this violates the “open closed principle”.
So what’s the problem?
As you can see, comp’s setXX() family of methods is tightly coupled to the Client API, and the Client needs the object creation details to be clear otherwise it can’t complete object creation.
So how do you decouple the object creation process from the client code?
If you remember the factory model, some of you might think, isn’t the factory model to solve this problem? The decoupling of object creation from object usage enables the client to deal with the corresponding factory class instead of the object creation process. Because there are multiple objects involved and there are relationships between them, the abstract factory pattern can be used here. So after optimization, you might implement something like:
Interface ComputerFactory{memory create memory (); CPU createCPU(); . }Copy the code
Then create different Factory implementation classes for different positioning computers.
Class Entry-level implements ComputerFactory{memory create memory (){produce 4G memory} CPUcreateCPU(){production i3 memory}... }Copy the code
The Client code is then changed to the following:
Public class Client{ComputerFactory factory; voidbuy(){ Computer comp = new Computer(); Comp.set memory (factory.create memory ()); comp.setCPU(factory.createCPU()); . } deliver the computer to the customer... }Copy the code
That way, if I need to upgrade the configuration, I can just pass in a different ComputerFactory and the business logic code doesn’t need to be touched. But there are problems with this solution for this requirement. Why is that?
We talked about the factory model earlier, the factory model is mainly used to produce a complete product. In other words, the object created using the factory pattern is the final product. Although the abstract factory pattern can create multiple products at once, the multiple products themselves are a complete final product, which can be used directly, except that the products created by the abstract factory pattern have some associations and belong to the same type of things.
However, in our demand, memory, CPU, mouse and so on are only a part of the whole computer, they are not a complete product, they need to be combined and assembled to form a complete computer object. Because in this requirement, we need to get a computer object, not memory, CPU, mouse, etc.
Of course you can use factory mode to create these parts, but then you have to assemble the parts yourself, which means you still need to understand the details of the object’s composition, otherwise you won’t be able to create a complete object. In addition, there will be one more part, one less part problem, will increase the complexity of the client.
And that’s where today’s hero, the Builder model, comes in.
Solve the demand
The scenario that the Builder pattern addresses is this requirement, aiming to assemble a series of trivial parts into a complete object, where the client does not need to know the exact assembly details.
In builder mode, there is an abstract interface that defines how to assemble a set of object parts (memory, CPU, mouse, etc.). An assembler (such as a computer seller) then assembles the parts and returns a complete object to the client. Take the example of building a computer above.
The implementation code is as follows:
Abstract class ComputerBuilder{protected Computer comp = new Computer(); Void build memory (); abstract void buildCPU(); Void build hard disk (); protected ComputergetComputer() {returncomp; }}Copy the code
Then, for computers of different levels, corresponding abstract interface implementation classes can be created to complete the assembly of computers. For example, a MacBook Pro needs to be assembled now, and the implementation is as follows:
Class MacBookProBuilder extends ComputerBuilder{void build () {comp.set ()"16g");
}
void buildCPU() {
comp.setCPU("i7"); } void build disk () {comp.set disk ("1T"); }}Copy the code
And you need someone to assemble it, like a computer seller,
class Seller{
ComputerBuilder builder;
Computer sell(){builder.build (); builder.buildCPU(); Builder. Build the hard disk ();returnbuilder.getComputer(); }}Copy the code
At this point the code in the Client becomes the following:
//MacBook Pro
public class Client{
Seller seller;
void buy(){ Computer comp = seller.sell(); } deliver the computer to the customer... }Copy the code
We find that the client is now decouple, we simply tell the computer seller what class of computer we need, and he will return us a complete assembled object, no longer needing to know how the parts were made (created in singleton mode? Or prototype? Factory model?) You don’t need to know how the parts fit together (does the CPU come first, or the hard disk?). . This is the perfect solution to the decoupling problem.
Now assume that Xiao Ming wants to change a computer configuration, only need to provide a corresponding Buidler subclass, complete the creation of corresponding parts, and then hand Builder to the person responsible for assembly (the term is Director), such as the seller, it is ok, Client no longer need to have any changes, in line with the “open and closed principle”.
If the object is not so complex, the Director can be omitted and the assembly process can be implemented directly on the Client side. Of course, in this case, the Client needs to understand the structure of the object, which may lead to the loss of arms and legs.
In addition, the above code could be written in a more elegant way.
Class MacBookProBuilder extends ComputerBuilder{void build () {comp.set ()"16g");
return this;
}
void buildCPU() {
comp.setCPU("i7");
returnthis; } void build disk () {comp.set disk ("1T");
returnthis; }}Copy the code
The current Builder object is returned in each assembly method so that the assembly logic in Director can be written directly in a clean and concise chained style.
class Seller{
ComputerBuilder builder;
Computer sell() {returnBuilder.build memory ().buildCPU().build disk ().getcomputer (); }}Copy the code
This chain style is a good solution to the anti-pattern problem of the flex constructor. What do you mean?
For example, now a certain class, attributes are very many, there are hundreds of……
Class A{attribute 1; Property 2; Property 3; 4; . Omit 100 attributes}Copy the code
Now I need to create that class object somewhere, and I also need to assign some of its properties. If we use the constructor at this time, it will be more troublesome, why?
Because I might have different properties each time I create them. Let’s say I need to create an A object somewhere in my application, using attributes 1, 2, 3, so I’m going to put A constructor in class A,
Class A {// omit attribute public A(attribute 1, attribute 2, attribute 3){}}Copy the code
In the other application you need to create an A object, you need to use properties 1, 2, 3, 4, so you need to add another constructor,
Class A {// omit attribute public A(attribute 1, attribute 2, attribute 3, attribute 4){}}Copy the code
As you can imagine, if there are too many reference constructors everywhere, and the parameters are not the same, the picture is too beautiful to imagine. Also, when constructors are heavily loaded, it can be a hassle to choose the right constructor when creating an object. At this point, the chain style of builder mode is a good solution to this problem.
class ABuilder{
A a = new A();
A setProperty 1 (XXX) {...returnthis; } AsetProperty 2 (XXX) {...returnthis; } AsetProperty 3 (XXX) {...returnthis; } AsetProperties of 4 (XXX) {...returnthis; } Abuild() {return a;}
...
}
Copy the code
If I need to change the number of properties set, I can easily adjust it. This is A good way to solve the problem of overloading the constructor.
A A = new ABuilder().set attribute 1(XXX),setSet attribute 3(XXX).set attribute 4(XXX).build();Copy the code
conclusion
This is the core of the Builder pattern, and we can summarize it.
-
The Builder mode is also used to create objects as the creator mode, and it will look similar to the factory mode, even indistinguishable.
-
In the Builder mode, the client only needs to interact with the Director and does not need to know the internal details of object construction and assembly, shielding the system from complexity.
-
You can add multiple Builders to the system, such as creating a Builder object for each brand of computer in the example above, to extend the functionality of the system. At the same time, fine-grained control and customization of each step in the assembly process can be easily achieved by adjusting the assembly process within each Builder.
But the key to the builder model is the separation of construction and assembly. The builder will eventually generate only one complete object, but this object is generally complex and divided into several modules. The builder pattern emphasizes the assembly process.
The factory pattern, on the other hand, focuses on creation, which may be simultaneous creation of one (simple factory or factory method pattern) or multiple objects (abstract factory pattern). Although the factory pattern can be complex to create, it doesn’t care if objects are assembled, just created.
The diagram below:
In a word, the factory model emphasizes creation, while the builder model emphasizes assembly.
In fact, the design pattern is not very absolutely very isolated to look at, so we do not need to distinguish each pattern is particularly clear, generally speaking, design pattern is not used independently, will match with each other. The factory mode and the Builder mode, for example, have different focuses, but can be used together. For example, when using the factory pattern to create objects, it is possible that each object will have a more explicit assembly process and can be used together. Conversely, in builder mode, the parts needed for each step of assembly may be created in factory mode (of course, it may be created in prototype mode, singleton mode).
Existing problems
No design pattern is perfect, and each design pattern has its own specific usage scenario. Through the above analysis, it is not difficult to find that
-
The builder pattern is mainly used to create a class of complex objects with obvious assembly process, and there is little internal structure difference in this class of objects, the basic structure is the same, and relatively stable. For example, the example of the computer above, no matter what brand of computer, what level of computer, accessories are those, CPU, memory, hard disk, etc., do not change a flower, no more than is the specific parameters of each accessory is different. As you can imagine, if there are two very different objects, one is a computer, one is a car, are completely different from each other, the assembly process is extremely different, naturally can not use the builder mode.
The concrete implementation of the various components of a class class or algorithm is often subject to change, but the algorithm that puts them together is relatively stable. The Builder pattern provides an encapsulation mechanism to insulate a stable combinatorial algorithm from its volatile components.
-
Objects created by the Builder pattern tend to change infrequently and infrequently. For things that change a lot, it’s not appropriate to use the builder model. When you buy a computer, you change the computer every three days and upgrade the configuration, and every time you upgrade the configuration, you need to create a new Builder class. If you change too much, you need to maintain a large number of Builder classes in the system, increasing the complexity and maintenance difficulty of the system.
The topic
I’ll finish with two examples I know of using the Builder pattern in real frameworks or JDK source code. In fact, the builder mode is also very widely used in the actual development, but also easier to identify, the basic named xxxBuilder is the use of the builder mode.
In the JDK, for example, the StringBuilder class is a classic implementation of the Builder pattern,
String s = new StringBuilder()"a").append("b").append("c").toString();
Copy the code
AbstractStringBuilder is derived from AbstractStringBuilder, an abstract class that defines an array of properties called values.
Abstract class AbstractStringBuilder implements Appendable, CharSequence {char[] value; // omit irrelevant code}Copy the code
When we call the Constructor of StringBuilder or the append() method, we’re actually operating on the value array. For example, append(),
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
Copy the code
It will call the append() method of the parent class, and return this to do chain-style programming. In the parent append() method, we end up calling the following:
Public AbstractStringBuilder appEnd (CharSequence s, int start, int end) {AbstractStringBuilder appEnd (CharSequence s, int start, int end)for (int i = start, j = count; i < end; i++, j++)
value[j] = s.charAt(i);
count += len;
return this;
}
Copy the code
It’s just setting the string value passed in to the value array. It’s kind of like an ArrayList.
When we call toString(), we simply return the contents of the value array as a String.
public String toString() {
return new String(value, 0, count);
}
Copy the code
In addition, The Builder mode is used extensively in Mybatis. When Mybatis is started, it will do a series of parsing work, such as Mybatis -config. XML file parsing, each mapper. XML, and annotations on the Mapper interface, etc. This series of parsing work is completed through a series of Builder. At the top is the BaseBuilder class. For example, XMLConfigBuilder is used to parse mybatis-config. XML files, XMLMapperBuilder is used to parse various mapper.xml files, etc.
In this case, BaseBuilder serves as a top-level abstract interface, where only some properties and methods of all concrete Builders are defined, such as the global Configuration object. The remaining Builder subclasses do their respective parsing as concrete builders. Take XMLConfigBuilder as an example. The xmlConfigBuilder.parse () method is the core method of parsing. This calls the parseConfiguration() method in BseBuilder. The parseConfiguration() method is the entire assembly process,
private void parseConfiguration(XNode root) {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
}
Copy the code
Taking propertiesElement as an example, this method will use XPATH to parse the Resource, URL properties in the Properties field and set them to the Configuration defined in the BaseBuilder. The rest of the analytical method is the same, not to repeat. When all the assembly work is complete, xmlConfigBuilder.parse () returns the parsed Configuration object. Xmlconfigbuilder.parse () is called in the build() method of SqlSessionFactoryBuilder, which generates the SqlSessionFactory from the assembled Configuration object. SqlSession is then created to manipulate the database.
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
returnbuild(parser.parse()); // omit exception handling}Copy the code
Well, that concludes today’s technical sharing of Builder mode. With the end of the Builder mode, the GOF23 creator mode is complete. Starting with the next installment, we’ll move into another broad category — the world of structural patterns. The pattern of the structural pattern focuses on how two or more objects can be combined to form a larger and more powerful object. In the next installment, I’ll start with one of the most important and least understood of structural patterns: the proxy pattern. 😊 👏