This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!
I think the most intuitive first impression of code is the following three main points:
- Code comment aspects. Not to mention the ability to be concise and clear, you can read my last article to write a doc document, which should be helpful.
- Code specification. This is a bit of a detail. The big factories have their own set of specifications, ranging from comma/space/carriage return positions to cyclomatic complexity/repetition rate/use case coverage, which I’ll talk about separately later.
- Code structure. Heap chengxiang code block is estimated to have some work experience in development, in the actual project has encountered. Which brings us to design patterns.
I wrote a lot of notes about design patterns, but today I put them out, but don’t take design patterns too seriously, or you may not be able to write code. In my opinion, design pattern is not something that can be mechanically applied. Don’t let its form bother you. Just understand its “intention” and don’t be poisoned.
If you’re tired of writing about design patterns, you can skip this section and not write about flowers. In the next section, I will devote several pages to slimming, lag monitoring, memory monitoring, and power optimization, so stay tuned.
Goto: Proxy mode
Goto: Decorator mode
Goto: Appearance mode
Goto: Adapter mode
Goto: command mode
Goto: Strategy mode
Goto: Template method pattern
Goto: State mode
Goto: Observer mode
Goto: Singleton pattern
The proxy pattern
Agents are one of the most basic design patterns. It can insert a “proxy” object that replaces the “real” object to provide additional or different operations. These operations typically involve communicating with the “real” object, so the “proxy” object usually acts as a middleman.
Objects that are brokered can be remote objects, objects that are expensive to create, or objects that require security control. Look at the class diagram structure:
This is the IObject interface. Both the real object and ObjProxy implement this interface:
/** * Provides interfaces for the actual object Tested and the proxy object TestedProxy */
public interface IObject {
void request(a);
}
Copy the code
RealObj is the object that actually handles the request() logic, but for design reasons you need to control access to method calls inside RealObj
public class RealObject implements IObject {
@Override
public void request(a) {
// Simulate some operations}}Copy the code
ObjProxy is a RealObj proxy class that also implements the IObject interface, so it has the same external methods. All client interactions with RealObj must go through ObjProxy.
public class ObjProxy implements IObject {
IObject realT;
public ObjProxy(IObject t) {
realT = t;
}
@Override
public void request(a) {
if (isAllow()) realT.request();
}
private boolean isAllow(a) {
return true; }}Copy the code
One day
The proxy and decorator patterns are almost identical in both class diagrams and code implementation, but why do we divide them? In fact, when learning design patterns, we should not stick to the format or memorize the form. What is important is to understand the intention behind the pattern. There is only one intention, but the forms of implementation may be various. This is why so many variants are still part of the XX design pattern.
The proxy pattern is intended to replace real objects for access control, while the decorator pattern is intended to add additional behavior to objects.
Decorator mode
For the definition of the decorator pattern, I refer directly to Head First: The decorator pattern dynamically appoints responsibility to objects. Decorators provide a more elastic alternative to inheritance when it comes to extending functionality.
The point of the decorator pattern is to dynamically assign additional responsibilities to objects. By combining objects, objects are decorated at runtime and new responsibilities are assigned to existing objects without changing any underlying code.
For example
Now we need to design a system for pancake sellers to make it easier for them to collect money. The da Ma mainly sells pancakes (7 yuan) and can add toppings sausage (1 yuan) and eggs (1 yuan). If business is booming, she may consider expanding her business to other snacks or add toppings. Now a system needs to be designed to quickly calculate the price of each customer’s snack.
plan
This is a food interface. As is often said, follow the OO principle of programming for interfaces, not implementations.
public interface ISnack {
String getDescription(a); // A description of food
double cost(a); // The price of food
}
Copy the code
This is specific to our pancake class.
public class PancakeSnack implements ISnack {
@Override public String getDescription(a) {
return "Pancakes";
}
@Override public double cost(a) {
returnPrice.PancakeSnack.price; }}Copy the code
This is a trivial class interface IDecoratorSnack, which inherits from the ISnack interface:
public interface IDecoratorSnack extends ISnack {
// Attributes can be extended according to the need, such as ingredients large, small, etc
}
Copy the code
The food class (decorator) can be used alone or wrapped around the ingredients class (decorator), and since the decorator and decorator objects have the same supertype, the decorated object can be used in place of the original object (the wrapped object) in any situation where it is needed.
So, I want a pancake with eggs for what price?
public class Egg implements IDecoratorSnack {
// Holds the food object to be modified
ISnack iSnack;
public Egg(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override public String getDescription(a) {
return iSnack.getDescription() + "Plus eggs.";
}
@Override public double cost(a) {
/* The price of the object to be modified + the price of the egg. * /
returniSnack.cost() + Price.Egg.price; }}Copy the code
For a snack with sausage, look at the code:
public class Sausage implements IDecoratorSnack {
ISnack iSnack;
public Sausage(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override public String getDescription(a) {
return iSnack.getDescription() + "+ sausage";
}
@Override public double cost(a) {
/* The price of the target + the price of the sausage */
returniSnack.cost() + Price.Sausage.price; }}Copy the code
Let’s collect the money. We can see that we can use the decorated object just as we would the original object, thanks to the same interface that decorator and decorator share. In the same way, an original object can be wrapped in multiple layers, but in the user’s eyes, it’s just an original object with new functionality, which is why I don’t say an Egg is a pancake + Egg:
public class DecoratorPatternTest {
public static void main(String[] ags) {
// Price: pancake + egg + sausage
ISnack snackOneISnack = new PancakeSnack();
snackOneISnack = new Egg(snackOneISnack);
snackOneISnack = newSausage(snackOneISnack); System.out.println(snackoneisnack.cost () + "yuan"); }}Copy the code
Each decorator holds an object of the decorator. The object of the decorator may not be the original object, but it may also be an object wrapped in layers. Through this combination, new behaviors are added.
It is important to note that by using the decorator pattern, you can create a large number of small classes in your design, which can complicate your program if overused. Another possible problem with typing is writing code that relies on specific decorator types and does not program against abstract interfaces.
The appearance model
The facade pattern provides a unified interface for accessing a set of interfaces in a subsystem. The facade defines a high-level interface that makes the subsystem easier to use. As abstract as it sounds, the essence of the facade pattern is to manage a group of objects within a subsystem in a composite manner. Its intent is to provide an interface that makes a subsystem easier to use.
For example
Suppose that the manufacturer’s ecological factory provides a series of intelligent devices and exposes the following interfaces to the public to facilitate remote control by users:
Here’s the smart light class from above:
public class Light {
public void on(a) {
System.out.println("Light");
}
public void off(a) {
System.out.println("Turn off the lights"); }}Copy the code
This is the intelligent air conditioning class:
public class AirConditioner {
public void on(a) {
System.out.println("Turn on the air conditioner.");
}
public void adjustTemperature(a) {
System.out.println("Regulating temperature");
}
public void off(a) {
System.out.println("Turn off the air conditioning."); }}Copy the code
This is the smart water heater category:
public class Heater {
public void on(a) {
System.out.println("Turn on the water heater for water and heat.");
}
public void adjustTemperature(a) {
System.out.println("Regulating temperature");
}
public void off(a) {
System.out.println("Turn off the water heater."); }}Copy the code
Under normal circumstances, users may need to perform the following operations at home, as shown in the following:
public class ClientTest {
private static Light mLight; / / intelligent lamp
private static AirConditioner mAc; // Intelligent air conditioning
private static Heater mHeater; // Smart water heater
public static void main(String[] ags) {
/** * For the user, the need to perform a series of operations, is not particularly cumbersome. * * < ul > < li > open light * < li > open air conditioning * * < li > < li > adjusting indoor temperature open the water heater for hot water * < / ul > < li > adjustment lukewarm * * /
mLight.on();
mAc.on();
mAc.adjustTemperature();
mHeater.on();
mHeater.adjustTemperature();
}
public ClientTest(Light light, AirConditioner ac, Heater heater) {
this.mLight = light;
this.mAc = ac;
this.mHeater = heater; }}Copy the code
This is very cumbersome and unfriendly to the user. The appearance pattern comes into being in this case. Appearances provide a simplified interface to users and decouple user classes from specific smart device classes.
plan
Let’s create a facade class:
public class HardwareFacade {
private Light mLight;
private AirConditioner mAc;
private Heater mHeater;
/** * Provides a simple interface to the intelligent hardware subsystem. * All the user needs to do when he gets home is call this one request, and * can enjoy the light, the water, and the chill. * /
public void openAppliance(a) {
mLight.on();
mAc.on();
mAc.adjustTemperature();
mHeater.on();
mHeater.adjustTemperature();
}
public HardwareFacade(Light light, AirConditioner ac, Heater heater) {
this.mLight = light;
this.mAc = ac;
this.mHeater = heater; }}Copy the code
The user only needs to interact with our HardwareFacade, which makes it much easier for the user. The appearance class realizes the management of the intelligent hardware subsystem through the combination, and makes the intelligent hardware and the user realize the decoupling. Of course, this does not affect the use of specific intelligent hardware classes by users alone, that is, the appearance provides a simple interface while still exposing the complete functions of the system for users to use when they need.
Let’s look at the calling code for the user class:
public class ClientTest {
public static void main(String[] ags) {
HardwareFacade facade = newHardwareFacade(light, ac, heater); facade.openAppliance(); }}Copy the code
One day
The look and feel are similar to the proxy pattern in that they both cache a complex entity and initialize it themselves. A proxy follows the same interface as its service object, making itself and the service object interchangeable, which is different from the look and feel.
Adapter mode
The adapter pattern transforms the interface of a class into another interface that the customer expects. Adapters allow classes with incompatible interfaces to work together. The intent of the adapter pattern is to transform the interface, which is important to distinguish the “extended behavior” of the decorator pattern from the “access control” of the proxy pattern.
For example
Existing hardware vendors offer a smart speaker that provides two “on” and “off” interfaces:
This is the smart Device interface class:
public interface Ihardware {
void on(a);
void off(a);
}
Copy the code
Here’s the specific smart speaker class:
public class SpeakerBox implements Ihardware {
@Override public void on(a) {
System.out.println("Turn on the speakers.");
}
@Override public void off(a) {
System.out.println("Turn off the speakers."); }}Copy the code
Now the manufacturer has upgraded the technology of the hardware, and it is found that the “shut down” interface is not commonly used, so it is replaced by the “sleep” interface. Now the intelligent device interface 2.0 is shown below:
public interface Ihardware2 {
void on(a);
void sleep(a);
}
Copy the code
Here’s the new smart Speaker class 2.0 concrete class:
public class SpeakerBox2 implements Ihardware2 {
@Override public void on(a) {
System.out.println("Experience version - Turn on the speakers.");
}
@Override public void sleep(a) {
System.out.println("Experience Version - Sleep Speaker"); }}Copy the code
Company A has always been A loyal user of the smart speaker of this manufacturer. After upgrading the smart speaker from 1.0 to 2.0 today, it found that the existing services could not run normally. After berating hardware manufacturers for altering the public interface, company A’s engineers had to make emergency fixes first.
plan
In order to be compatible with older versions and with as few code changes as possible, engineer Wang proposed: we need to make incompatible interfaces compatible. How do you translate the 2.0 interface into the 1.0 interface that customers expect? The adapter object is shown below:
public SpeakerBox2Adapter implements Ihardware {
private SpeakerBox2 mSb2;
public SpeakerBox2Adapter(SpeakerBox2 sb2) {
this.mSb2 = sb2;
}
@Override public void on(a) {
mSb2.on();
}
@Override public void off(a) {
/* Of course there can be a series of conversion logic */mSb2.sleep(); }}Copy the code
With the adapter object, we can use SpeakerBox2 objects just like SpeakerBox. The test code is shown below:
public class ClientTest {
public static void main(String[] ags) {
Ihardware hw1 = new SpeakerBox();
hw1.on();
hw1.off();
Ihardware hw2 = new SpeakerBox2Adapter(newSpeakerBox2()); hw2.on(); hw2.off(); }}Copy the code
One day
The facade pattern defines a new interface to an existing object, while the adapter pattern attempts to leverage an existing interface. Adapters typically encapsulate only one object, and skins typically apply to the entire object subsystem. The intent of the adapter pattern is to transform interfaces, which is what distinguishes the “extended behavior” of the decorator pattern from the “access control” of the proxy pattern.
Furthermore, adapters can modify the interface of an existing object, and decorative patterns can enhance the functionality of an object without changing its interface. In addition, decorations support recursive composition, which adapters do not.
Adapters can provide different interfaces for encapsulated objects, proxy patterns can provide the same interface for objects, and decorators can provide enhanced interfaces for objects.
Command mode
The command pattern encapsulates “requests” into objects so that different requests can be used. The goal is to completely decouple the object that initiates the request from the object that actually performs the processing of the request, and to flexibly extend the receiver object with different operations.
For example
A manufacturer has released a remote control that claims to control all of its hardware. You get a smart desk lamp at home, press the “smart button” button, ah light, and press down, the light out.
This is the smart light class, currently there are only two methods, namely the light on and light off operations:
public class Light {
public void on(a) {
System.out.println("Light");
}
public void off(a) {
System.out.println("Turn off the lights"); }}Copy the code
Now that this is our remote Control class, our first thought might be to create a smart hardware interface directly to Ihardware, so that our Control directly holds Ihardware:
public class Control {
Ihardware mHardware;
/** Click the smart button */
public void btnPressed(a) {
if ("Open logic") { mHardware.on() }
else if{mhardware.off ()}}}Copy the code
But when you press the button on the purifier again, the purifier begins to “breathe itself,” and you find that the Ihardware interface is no longer suitable as the number of weird interfaces on the hardware increases.
public class AirCon {
/ * ventilation * /
public void changeOfAir(a);
}
Copy the code
plan
For the above problem, we can create a command object interface:
public interface Command {
public void execute(a);
}
Copy the code
This is the concrete intelligent light Command object, which is the implementation class of Command. We encapsulate the “light on” operation as a request class:
public class LightOnCommand implements Command {
private Light mLight;
public LightOnCommand(Light light) {
mLight = light;
}
@Override public void execute(a) { mLight.on(); }}Copy the code
We also encapsulate the request “receiver turns off the lights” as an object.
public class LightOffCommand implements Command {
private Light mLight;
public LightOnCommand(Light light) {
mLight = light;
}
@Override public void execute(a) { mLight.off(); }}Copy the code
This is our remote control class, which dynamically executes commands in the following way. See the code below:
public class Control {
private Command mComBtn;
public void setCommand(Command comBtn) {
mComBtn = comBtn;
}
public void btnPressed(a) { mComBtn.execute(); }}Copy the code
In this way, the requesting object (the Control class) and the receiver (the Light) are completely decoupled. the two communicate through the Command object (the Command implementation class). The Command object encapsulates a set of actions of the receiver, of course, can be multiple groups, here is the “smart Light”, “smart Light” operation. Let’s test it out:
public class Test {
public static void main(String[] ags) {
Control control = new Control();
Light light = new Light();
// Intelligent button on the light operation
control.setCommand(new LightOnCommand(light));
control.btnPressed();
// Intelligent key off light operation
control.setCommand(newLightOffCommand(light)); control.btnPressed(); }}Copy the code
At this time to think about the “automatic ventilation” function, is it become simple? Instead of changing the Control object at all, we just need to wrap the “auto breathe” request into an object called AirConCommand.
One day
We can use a command to convert any operation to an object, and the parameters of the operation will become member variables of the object. Of course, this will also be the same as decorator mode, will cause a large number of small categories, so the specific selection needs a full range of measurement and judgment.
The strategy pattern
The policy mode defines the parameters of an algorithm and makes them interchangeable. This mode enables the algorithm to change independently of the object using the algorithm.
For example
Jingle Bells, wechat group ~
Xiaowang: The park will be built on Saturday. See you for the arrangement.
- 09:00-09:15 collection
- 09:15-11:00 mountaineering
- 11:00-13:00 Lunch at the top of the mountain
Xiaowang: Time is tight, please arrange the route by yourself
-
The climb is the shortest and steeper, xx->xx
-
Suitable for taking photos, beautiful scenery, xx->xx
-
The mountain road is flat, xx->xx
plan
Here is the policy interface for the uphill route:
public interface IStrategyRoutes {
// Mountain route information
void mcRoutesInfo(a);
}
Copy the code
This is route A, the shortest route for employees. Climbing route is different, this is also the demand (collection – climbing – lunch) each employee change part of the reason we put each specific lines up separate encapsulated, rather than add method one by one to the respective staff inside the class, because of considering the code reuse, conforming with the change of encapsulation OO principles:
public class ShortestWalkRoutes implements IStrategyRoutes {
@Override public void mcRoutesInfo(a) {
System.out.println("Mountaineering: The shortest journey up the mountain."); }}Copy the code
This is route B provided for employees, suitable for taking photos, with beautiful scenery:
public class CameraRoutes implements IStrategyRoutes {
@Override public void mcRoutesInfo(a) {
System.out.println("Mountaineering: Good for photos, beautiful scenery."); }}Copy the code
This is route C for employees, easy on the mountain:
public class EasyRoadRoutes implements IStrategyRoutes {
@Override public void mcRoutesInfo(a) {
System.out.println("Mountaineering: Easy walking on mountain roads"); }}Copy the code
Similarly, we can isolate the commonalities of each employee. Add an interface type variable to the StaffBase class (instead of the specific route up the hill). Each employee will set this variable dynamically to refer to the correct behavior type at runtime (e.g., minimal walking, good for taking photos, etc.), so we should program for the interface, not for the implementation.
This is the base class for an employee class:
public abstract class StaffBase {
IStrategyRoutes routes; / / strategy
public void setRoutes(IStrategyRoutes routes) {
this.routes = routes;
}
public void showStaffRoutes(a) { routes.trafficRoutesInfo(); }}Copy the code
When climbing, employees can choose their own route, such as employee Li Si. The static factory approach is used to decouple the employee code from the route object creation code. To add other employees, we only need to add a new class and inherit the StaffBase class. No other code changes are required, which conforms to the principle of open for extension, closed for modification.
public class StaffOne extends StaffBase {
public StaffOne(a){
// Employee Li Si: Route 1 was chosen by the factory method
routes = RoutesFactory.getRoutes(1); }}Copy the code
For the final test class, print StaffOne’s route plan:
public class StrategyPatternTest {
public static void main(String[] args) {
message("Set");
StaffBase staff1 = new StaffOne();
staff1.showStaffRoutes();
message("Lunch"); }}Copy the code
Going back to the definition of the policy pattern, we define the parameters of the algorithm and encapsulates them so that they are interchangeable. This pattern allows the algorithm to change independently of the customers who use the algorithm.
In this case, there are different routes to reach the top of the mountain, all of which can achieve the purpose of reaching the top. We can call them strategies. However, the specific route strategy change has no impact on the group building activities, and employees can freely choose according to their needs.
Template method pattern
The template method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. The template approach allows subclasses to redefine certain steps in an algorithm without changing the structure of the algorithm.
This is similar to the policy pattern above, except that the policy pattern encapsulates replaceable behaviors and then uses a combination to decide which behaviors to adopt. The intent is to provide a set of replaceable algorithms that vary independently of the customers using the algorithms, i.e. the user can choose different algorithms.
In our template method pattern, we provide an outline of an algorithm, and let subclasses decide how to implement the steps of the algorithm, that is, the user can decide how to implement the steps of the algorithm, but can not change “different” algorithms.
For example
Let’s take online shopping as an example. When shopping, there are generally the following steps:
I now create a shopping class and encapsulate the shopping process in a method. This method is called a “method” in the template method pattern. Let’s see why it is called a template method pattern, not a template pattern.
The method is decorated here with the final keyword to prevent subclasses from tampering with the algorithm template. Notice that this class is abstract and contains two abstract methods, “order” and “deliver,” with the intent that concrete subclasses determine the implementation details. Not only that, “shipping” and “receiving” two methods, also embodies the idea of code reuse.
public abstract class OnlineShopping {
final void shoppingProcess {
order(); / / order
delivery() / / delivery
distribution(); / / distribution
receipt(); / / receiving
}
abstract void order(a);
void delivery(a) {
message("Seller delivers.");
}
abstract void distribution(a);
void receipt(a) {
message("Buyer received."); }}Copy the code
Next create a specific east mall shopping class. JDShopping implements the abstract method of the OnlineShopping class. As you can see, we are not breaking the algorithm template or changing the order of the algorithm, we are just giving the steps a “fit”, that is, defining different implementation details for each step.
public class JDShopping extends OnlineShopping {
@Override void order(a) {
System.out.println("An East self-operated store orders");
}
@Override void distribution(a) {
System.out.println("Use an East Express to deliver."); }}Copy the code
Similarly, to create a specific mall shopping class:
public class TBShopping extends OnlineShopping {
@Override void order(a) {
System.out.println("Order from a small store");
}
@Override void distribution(a) {
System.out.println("Using a wind express delivery"); }}Copy the code
Let’s run a test:
public class ClientTest {
public static void main(String[] ags) {
// The shopping process of an East mall
JDShopping jd = new JDShopping();
jd.shoppingProcess();
// The shopping process of a treasure mall
TBShopping tb = newTBShopping(); tb.shoppingProcess(); }}Copy the code
One day
The template method pattern is based on inheritance: it allows you to change part of the algorithm by extending part of the subclass. Policies are based on a combination mechanism: you can change parts of an object’s behavior by providing different policies for the corresponding behavior. The template method operates at the class level, so it is “static.” Policies operate at the object level, thus allowing the behavior to be switched at runtime.
The state pattern
The state mode allows an object to change its behavior when its internal state changes, so that the object looks as if it has modified its class. The intent of the state pattern is to encapsulate the state, encapsulate it as a separate class, delegate action to the current state object, and let the current state change between state objects, thus allowing us to change the behavior of the class by referring to different state objects in a combination.
For example
Taking the subway ticket vending machine as an example (which is different from the real process, the emphasis here is on the idea of encapsulation state), the user can perform the following operations: coin operation, refund, purchase, and ticket issuance 4 actions, and these actions will have different behaviors depending on the user’s state. Such as:
- In the non-coin-operated state, the user can choose to insert the coin and enter the coin-operated state, but can not refund, buy tickets, print tickets;
- In the coin-operated state, the user can continue to insert the coin, or return the refund to the non-coin-operated state, or purchase the ticket to enter the ticket purchasing state, but can not directly print the ticket.
- In the ticket purchasing state, it can also be understood as the state of submitting the order. If the ticket is successfully purchased, the ticket will be issued automatically and return to the non-coin-operated state.
If we don’t use the state mode, you’ll find that every action is accompanied by a lot of if judgments. What if we extend the “non-operational time state”? Or more than one? I’m going crazy.
public class TicketMachine {
static final NoSlotStateType = 0; // No coin is in operation
static final SlotStateType = 1; // Coin-operated
static final BuyingTicketStateType = 2; // Ticket status
/** * Take the coin-op as an example: * What if we add other states? * Does every method need to be redefined? * /
public void toSlot(int money) {
// The current status is not coin-operated
switch (curState) {
case NoSlotStateType:
curState = SlotState;
break;
case SlotStateType:
message("Coining again!");
break;
case BuyingTicketStateType:
message("This state does not support coin-op!");
break; }}... }Copy the code
plan
State patterns are an alternative that can be thought of as not requiring multiple conditions. We separate each state into a class, and within each state object, each responds to the user’s actions. The following is the automatic ticket machine class realized by the state mode:
public class TicketMachine {
/** * This is the concrete implementation of the state class. * See below {for details@codeNoSlotState}. * /
NoSlotState noSlotState; // No coin is in operation
SlotState slotState; // Coin-operated
BuyingTicketState btState; // Ticket purchasing status
/* Can be dynamically specified to the specific execution class according to the state */
TicketState curState; // The current state is abstract
/** * initializes the current state to a coin-free state in the construct. * /
public TicketMachine(a) {
this.curState = noSlotState;
noSlotState = new NoSlotState(this);
slotState = new SlotState(this);
btState = new BuyingTicketState(this);
}
/** * delegate the action to the current state object. * /
public void toSlot(int money) {
curState.toSlot();
}
public void refund(a) {
curState.refund();
}
public void tickets(a) {
curState.tickets();
// When the purchase request is successful, the ticket will be issued automaticallyThe printTicket (); }public void printTicket(a) {
curState.printTicket();
}
/** * The current state changes between state objects. * /
public void setState(TicketState state) {
this.curState = state;
}
/** * provides the get method to prevent coupling between state objects. * /
public TicketState getNoSlotState(a) {
return noSlotState;
}
public TicketState getSlotState(a) {
return slotState;
}
public TicketState getBuyingState(a) {
returnbtState; }}Copy the code
This is our state interface class. We use abstract classes because we can put the processing logic that is reused within each state in a superclass.
public abstract class TicketState {
/** * This is the coin operation. * * We extract the user's action. * State mode is an alternative to multiple conditions. * In each state, the user is likely to perform the following * actions. Remind the user if they are not compliant. * /
abstract void toSlot(int money);
/** * This is a refund action. * /
abstract void refund(a);
/** * This is the action of buying tickets. * /
abstract void tickets(a);
/** * This is the ticket printing action. * /
abstract void printTicket(a); . }Copy the code
This is our uncoin-operated homepage class. You can see that in this class, all the actions that the user might trigger in the “non-coin-operated state” are handled, and the status of the ticket machine is updated at any time.
public NoSlotState implements TicketState {
TicketMachine tm;
public NoSlotState(TicketMachine tm) {
this.tm = tm;
}
/** * After the operation, it will become the coin-operated state. * /
@Override public void toSlot(int money) {
tm.setState(tm.getSlotState());
}
/** * In the state of no coin, no refund and other operations. * /
@Override public void refund(a) {
System.out.println("You haven't put a coin in yet!");
}
@Override public void tickets(a) {
System.out.println("Need to operate after the coin!");
}
@Override public void printTicket(a) {
System.out.println("Need to operate after the coin!"); }}Copy the code
Here is our coin-operated status class:
public SlotState implements TicketState {
TicketMachine tm;
public SlotState(TicketMachine tm) {
this.tm = tm;
}
/** * Supports multiple coin inserts before purchase. * /
@Override public void toSlot(int money) {
tm.setState(tm.getSlotState());
}
/** * Refund can be applied before ticket purchase. * /
@Override public void refund(a) {
tm.setState(tm.getNoSlotState());
}
/** * If the amount is sufficient, a ticket purchase request will be initiated. * /
@Override public void tickets(a) {
if (isEnough)
tm.setState(tm.getBuyingState());
else
System.out.println("Not enough money!");
}
@Override public void printTicket(a) {
System.out.println("Enough tickets after the operation!"); }}Copy the code
Here is our ticket status class:
public BuyingTicketState implements TicketState {
TicketMachine tm;
public BuyingTicketState(TicketMachine tm) {
this.tm = tm;
}
/** * Do not support coin operation, refund, repeat submission and other operations. * /
@Override public void toSlot(int money) {
System.out.println("In the ticket, do not support the coin!");
}
@Override public void refund(a) {
System.out.println("Ticket issued, no refund!");
}
@Override public void tickets(a) {
System.out.println("Duplicate print requests are not supported.");
}
/** * After the vote is successfully cast, the switch state is no vote. * /
@Override public void printTicket(a) {
System.out.println("Ticket issued!); tm.setState(tm.getNoSlotState()); }}Copy the code
As we can see from the above code, we not only avoid a lot of if judgment in TicketMachine class, but also encapsulate and pull out the “major changes”, so that the current existing state is closed to modification.
Of course state patterns also create a lot of small classes, which is a price to pay for elasticity, but it’s definitely worth it. What really matters is the number of classes we expose to the customer, not the number of our own classes, and we can hide these extra state classes from the outside world.
One day
State can be thought of as an extension of policy. Both are based on the composition mechanism: both change the behavior of a helper object in different situations by delegating part of the work to it. The policy makes these objects completely independent of each other, unaware of the existence of other objects. However, the state mode does not limit the dependencies between specific states and allows them to change their state in different situations.
Observer mode
The observer pattern defines one-to-many dependencies between objects so that when an object changes state, all of its dependencies are notified and automatically updated. We usually refer to stateful objects as topics, and dependency objects that are notified by topics as observers. A one-to-many relationship is defined between the topic and the observer. The observer is dependent on the topic and is notified whenever the state of the topic changes.
For example
Taking the official lottery website as an example, lottery users can register or cancel their registration freely. When the lottery numbers are updated, the status of the official website changes, every registered lottery user will receive a notice from the official website. Here the official website is equivalent to what we call the theme, the lottery people are equivalent to our observers.
We can start by creating a topic interface class:
/** * This is the topic class. * As long as users register with it, when the theme status changes, * can receive lottery information from the official website. * /
public interface ISubject {
/** Registration and removal methods for lottery users */
void registerObserver(IObserver o);
void removeObserver(IObserver o);
/** Send "lottery information change notification" to users */
void notifyLottery(a);
}
Copy the code
The reason for using interfaces, rather than concrete topic classes directly, is that you don’t want the topic to be overly coupled to the observer, and you try to minimize the interdependencies between objects so that you can cope with change and build a resilient OO system.
This is an observer interface that has only one updateLottery method, which is called when the theme’s state changes:
/** * Observer interface class. * * All observers must implement the interface, everything about the observer * the topic only knows that the observer implements the current interface, IObserver * The topic does not need to know who the observer's concrete class is, what it does, or any other details * this makes the dependency between the topic and the observer very low. * /
public interface IObserver {
/** What to do when the lottery information is updated */
void updateLottery(Lottery lottery);
}
Copy the code
This is a concrete topic class. A concrete topic always implements the topic interface. In addition to the registration and unregistration methods, a concrete topic also implements the notifyLottery() method, which is used to update all current observers when the status changes.
public class LotteryData implements ISubject{
private ArrayList<IObserver> list = new ArrayList<>();
/* Lottery information class, as required before notify refresh */
private Lottery lottery;
@Override public void registerObserver(IObserver o) {
list.add(o);
}
@Override public void removeObserver(IObserver o) {
final int index = ;
if((index = list.indexOf(o)) ! = -1) { list.remove(index); }}@Override public void notifyLottery(a) {
for(IObserver o : list) { o.updateLottery(lottery); }}/** * Analog intelligent lottery machine to start the lottery. * /
public void beginWork(a) {
new Timer().schedule(() -> notifyLottery(), 0.5000); }}Copy the code
This is the physical class of the ticket, including the date of the ticket and the current winning number:
public class Lottery {
/** Date of the lottery ticket */
private Date date;
/** The winning numbers */
private int winningCount;
}
Copy the code
This is the specific observer lottery player 1, the observer must implement the IObserver interface and register the specific topic in order to receive updates:
public class LotteryBuyerOne implements IObserver{
public LotteryBuyerOne(ISubject s) {
s.registerObserver(this);
}
@Override public void updateLottery(Lottery lottery) {
System.out.println("Winning number:"+lottery.winningCount); }}Copy the code
We can add observers as much as we want, because the observer and the topic are loosely coupled, so if we change either the observer or the topic, it doesn’t affect the other. Let’s put this design to the test.
public class ObserverPatternTest {
public static void main(String[] args) {
final LotteryData subject = new LotteryData();
final LotteryBuyerOne loOne = newLotteryBuyerOne(subject); subject.beginWork(); }}Copy the code
Built-in Observer mode
In addition to implementing a full set of observer patterns ourselves, Java provides a built-in observer pattern. The java.util package contains the basic Observer interface and Observable class, similar to our Observer and Subject interfaces. Let’s look at the same scenario in built-in observer mode:
This is a concrete subject class, and because Observable is a concrete class and not an interface, it is not very scalable, limiting Observable’s reuse potential.
public class LotteryData extends Observable {
private Lottery lottery;
public void beginWork(a) {
new Timer().schedule(() -> updata(), 0.5000);
}
public Date getDate(a) {
return lottery.date;
}
public int getWinningCount(a) {
return lottery.winningCount;
}
private void updata(a) {
setChanged(); // Change the state
notifyObservers(this); // Notify the observer}}Copy the code
Observable provides notifyObservers() and notifyObservers(Object ARg), so if you are obsered about pushing data to the observers, you can pass the data Object directly to an update method with a parameter, If you want the observer to pull the data, just call the no-argument update method and provide the public GET method. This is the specific observer lottery player 1:
public class LotteryBuyerOne implements Observer{
public LotteryBuyerOne(Observable observable) {
observable.addObserver(this);
}
@Override public void update(Observable o, Object arg) {
// When the status of the lottery changes, the lottery users need to be notified
if(o ! =null && o instanceof LotteryData){
LotteryData lotteryData = (LotteryData)o;
System.out.println("The winning numbers are :"+ lotteryData.winningCount); }}}Copy the code
Let’s test this design. Note that with the built-in observer mode, the order of notifications is different from the order in which we register, so we cannot use the built-in observer mode when we have a requirement for the order of notifications.
public class BuiltInObserverPatternTest {
public static void main(String[] args) {
final LotteryData lotteryData = new LotteryData();
final LotteryBuyerOne lBuyerOne = newLotteryBuyerOne(lotteryData); lotteryData.beginWork(); }}Copy the code
The singleton pattern
The singleton pattern ensures that there is only one instance of a class and provides a global access point. Singleton mode, according to the loading time can be divided into: hungry way and lazy way. Let’s look at it in detail:
Lazy loading
This is the easiest lazy way to do it, but it’s not thread-safe. So if multithreading is involved in your project, avoid it. Lazy loading has the advantage of high resource utilization, but the initialization on the first call causes a performance delay, and then every time the instance is obtained, it has to determine whether the instance is initialized first, which causes some efficiency loss.
public class Singleton {
private static Singleton singleton;
/** * Private construct, providing the instance externally only through getInstance */
private Singleton(a) {}public static Singleton getInstance(a) {
if (singleton == null) {
singleton = new Singleton();
}
returnsingleton; }}Copy the code
What if we added the synchronized modifier to the code above? Adding the synchronized modifier solves the problem of thread insecurity, but it introduces another problem: execution efficiency. Each thread executes the getInstance() method to get an instance of the class, and synchronization becomes unnecessary once the instance is actually created.
public class Singleton {
private static Singleton singleton;
private Singleton(a) {}public static synchronized Singleton getInstance(a) {
if (singleton == null) {
singleton = new Singleton();
}
returnsingleton; }}Copy the code
From what we’ve seen above, it’s natural to narrow down the synchronization area in order to stay synchronized and be efficient. So that’s the way to synchronize blocks.
public class Singleton {
private static Singleton singleton;
private Singleton(a) {}/** * This is a thread problem. * * Multiple threads may pass the first check at the same time, * causing different objects to be created. * /
public static Singleton getInstance(a) {
// First check to avoid unnecessary synchronization
if (singleton == null) {
synchronized (Singleton.class) {
singleton = newSingleton(); }}returnsingleton; }}Copy the code
In the case above, we add a second check to ensure that the multithreading problem occurs. See the code below:
public class Singleton {
private static Singleton singleton;
private Singleton(a) {}
public static Singleton getInstance(a) {
if(Singleton == null) {// First check to avoid unnecessary synchronization
synchronized (Singleton.class) {
if(Singleton == null) {// Second check, thread safety
singleton = newSingleton(); }}}returnsingleton; }}Copy the code
Is this the place to rest? No, double-checked locking (DCL) can fail because the Java compiler allows processors to execute out of order. Fortunately, after JDK1.5, Java provided the volatile keyword to ensure the order of execution, thus making singletons work. Below JDK1.5, we should avoid using this approach. The double-checked locking singleton code is shown below:
public class Singleton {
private volatile static Singleton singleton;
private Singleton(a) {}
public static Singleton getInstance(a) {
if(Singleton == null) {synchronized (Singleton.class) {
if(Singleton == null){
singleton = newSingleton(); }}}returnsingleton; }}Copy the code
The keyword volatile is used because volatile ensures visibility in the application. If you declare a field as volatile, all read operations will see the change as long as a write operation is performed on the field. This is true even if local caching is used; volatile fields are immediately written to main memory, where reads occur (excerpted from Java programming ideas). In fact, it is to prevent the compiler to optimize the execution of instructions, to prevent the reordering of instructions, so that every operation is to main memory to read and write the same data, to prevent problems.
The hungry load
Huns are inherently thread-safe. In the case of only one classloader, the hunhune method initializes the instance when the class is loaded while the system is running, leaving the JVM virtual machine to ensure that the initialization methods of a class are properly locked and synchronized in a multi-threaded environment. The class instantiates the static object at the same time as the class is created. The resources are initialized, so the first call is faster. The advantage is speed and reaction time, but regardless of whether the single call will not be used, it will occupy some memory for the duration of the program.
public class Singleton {
public static final Singleton instance = new Singleton();
private Singleton(a) {}}Copy the code
Hangry loading of static code blocks is also thread safe.
public class Singleton {
private static final Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (a) {}public static Singleton getInstance(a) {
returninstance; }}Copy the code
This is the way we usually write it, just like the two above.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(a) {}public static Singleton getInstance(a) {
returninstance; }}Copy the code
Static inner class loading
The InterSingleton class is not initialized when the InterSingleton static inner class is not used. The InterSingleton class is only loaded when the getInstance() method is explicitly called, thus instantiating the object. So it can achieve the goal of delayed instantiation. Creating singletons in static initializers also ensures thread safety. This approach also avoids the pitfalls of double-checking locking when JDK versions are earlier than 1.5.
public class Singleton {
private Singleton(a){}
public static Singleton getInstance(a) {
return InterSingleton.singleton;
}
private static class InterSingleton {
private static Singleton singleton = newSingleton(); }}Copy the code
Enumeration methods
Enumerations are not only thread safe by default when creating instances, but also automatically prevent the creation of new objects during deserialization, reflection, and cloning. Enumerated classes are also instantiated on the first access, which is lazy loading. Since enumerations are a JDK 1.5 feature, double lock checking is done in the same way, subject to version requirements.
/** * enumerations can add their own properties and methods, just like ordinary classes in Java. * /
public enum Singleton {
INSTANCE;
}
Copy the code
Map register singleton
As projects become more complex, we may need to manage multiple singletons for different businesses simultaneously. In this case, we can use the Map container to manage the singleton, using the unified interface to obtain the corresponding singleton:
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private SingletonManager(a) {}private static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
returnobjMap.get(key); }}Copy the code
Well, due to the influence of the length of the article, I cannot write the following design pattern in this section. I will share it next time. If you like my post, give me a thumbs-up, and that’s what keeps me (Battler) going.
- See Head First Design Pattern
- Refer to Deep Design Patterns