Factory Mode--The Evolution of Cat Grain Plant

Keywords: Java Design Pattern Interview

The Birth of Cat Food Company

Gyroscope is a program. It started its own company. In order to commemorate the years when dream was born, the company named "Running Yard" and its main business is cat food production.

A Miao that takes R&D and operations into account is not a long-term solution. So I hired a vegetable mew to be an apprentice. I don't care how the technology works. The name of vegetable mew is the most important thing for gyroscopes - recruiting money.

Quickly, the first product, fish civet cat food, came online, and gyroscope enabled money to write an online order system for customers to place their orders online

Recruiting money quickly wrote the code

/**
 * Fish-scented Cat Food
 *
 * @author Cicada bathing
 */
public class FishCatFood {

    //Cat Food Taste
    private String flavor;

		//Process of Cat Food Production
    public void make() {
        System.out.println("Making [" + flavor + "]Tasteful Cat Food");
    }

    public String getFlavor() {
        return flavor;
    }

    public void setFlavor(String flavor) {
        this.flavor = flavor;
    }

    public FishCatFood(String flavor) {
        this.flavor = flavor;
    }
}
public class PaoMaChang {

    public FishCatFood order() {
        FishCatFood fishCatFood = new FishCatFood("fish");
        fishCatFood.make();
        return fishCatFood;
    }
}

Go online after the test and run normally.

After a while, the gyro said to the financing: "The company is currently developing a beef cat food and is expecting to launch new products such as mint cat food and chicken cat food in the coming period of time. You can upgrade your order system to cope with possible changes in the future."

Recruiting money received the task of refactoring the original code, first creating an abstract CatFood, then all specific flavors of cat food must inherit it

/**
 * An abstract class of cat food from which all specific flavors of cat food must be inherited
 *
 * @author Cicada bathing
 */
public abstract class CatFood {
  	//Product flavor
    String flavor;

    public abstract void make();
}

Next come the different tastes of cat food

/**
 * Beef Cat Food
 */
public class BeefCatFood extends CatFood {
    public BeefCatFood() {
        this.flavor = "beef";
    }

    @Override
    public void make() {
        System.out.println("Making [ beef]Cat food taste");
    }
}

/**
 * Chicken and cat food
 */
public class ChickenCatFood extends CatFood {
    public ChickenCatFood() {
        this.flavor = "chicken";
    }

    @Override
    public void make() {
        System.out.println("Making [ chicken]Cat food taste");
    }
}

/**
 * Fish and cat food
 */
public class FishCatFood extends CatFood {
    public FishCatFood() {
        this.flavor = "fish";
    }

    @Override
    public void make() {
        System.out.println("Making [ fish]Cat food taste");
    }
}

/**
 * Mint Cat Food
 */
public class MintCatFood extends CatFood {
    public MintCatFood() {
        this.flavor = "mint";
    }

    @Override
    public void make() {
        System.out.println("Making [ mint]Cat food taste");
    }
}

Last is the logic for placing an order

public class PaoMaChang {

    public CatFood order(String flavor) {

        CatFood catFood;

        if ("fish".equals(flavor)) {
            catFood = new FishCatFood();
        } else if ("beef".equals(flavor)) {
            catFood = new BeefCatFood();
        } else if ("mint".equals(flavor)) {
            catFood = new MintCatFood();
        } else if ("chicken".equals(flavor)) {
            catFood = new ChickenCatFood();
        } else {
            throw new RuntimeException("No cat food found for this taste");
        }

        catFood.make();

        return catFood;
    }
}

Money can't wait to show the gyroscope its code and say, "Boss, my code has been able to meet future dynamic changes. If you have a new product, you just need to create the object of the product and then modify the order() method!"

The gyro nodded admiringly.'It's great to see that you've thought carefully about yourself! But don't worry, have you heard about the open-close principle?'

"Open Close Principle? I've heard about it, but just conceptually, I remember it was like"Close to Modifications, Open to Extensions". I was still familiar with it for the interview, Ha-ha"

"Then take a look at your code against the on/off principle. What do you think is wrong with your code?" asked the gyro.

Recruitment took a close look at its code, "I know the problem now is that once a new product comes online, you need to change orde() Method, this is the so-called no closing of modifications, but there is a new product you must have a place to get him out of the new one. This step can not be omitted in any way, I think the current code can meet the needs."

"You're right, design principles are not a golden rule. For example, if only a few new flavor products come online in the future, you really don't need to change the code structure now, simply modify order() That's fine. You don't care about the constraint of closing modifications at all. But you need to think about what you would do if we developed dozens or hundreds of products later on?

"I really couldn't think of any other way than modifying the order() method...", answered the screwdriver with money.

The gyroscope explained slowly: "At this point, we can first identify which parts of the code are constantly changing, and then consider using encapsulation. Obviously, the part of the object created in the order() method often needs to change, and we can encapsulate it so that it is dedicated to creating objects."

/**
 * Simple factory for cat food
 * @author Cicada bathing
 */
public class SimpleCatFoodFactory {
    public static CatFood createCatFood(String flavor) {
        CatFood catFood;

        if ("fish".equals(flavor)) {
            catFood = new FishCatFood();
        } else if ("beef".equals(flavor)) {
            catFood = new BeefCatFood();
        } else if ("mint".equals(flavor)) {
            catFood = new MintCatFood();
        } else if ("chicken".equals(flavor)) {
            catFood = new ChickenCatFood();
        } else {
            throw new RuntimeException("No cat food found for this taste");
        }

        return catFood;

    }
}


/**
 * order code after refactoring
 */
public class PaoMaChangV2 {

    public CatFood order(String flavor) {

        CatFood catFood = SimpleCatFoodFactory.createCatFood(flavor);

        catFood.make();

        return catFood;
    }
}

The gyroscope explained: "So we did the encapsulation, focusing on the SimpleCatFoodFactory to generate the object."

Recruitment immediately raised its own question: "I don't understand the benefits of doing this. In my opinion, it just moved a problem into one object. The problem itself still exists!"

"You're absolutely right about the process of creation." The gyro nodded, "But we still get a lot of benefits, and now our SimpleCatFoodFactory can be more than just order() The method is used, and any subsequent logic can call the class we write, and if we need to change it later, we just need to change this separate class.

Recruiting money helplessly responded,'Well, what you're saying makes sense. It's a good code optimization practice to pull out the parts that change a lot. By the way, did you just have a name for this optimization technique?'

"This is called a simple factory. Many developers mistakenly think it is a design mode, but it is not a GoF23 design mode. But because there are too many people, it is often introduced with the factory mode. Whether it is a design mode or not is not important to us."

Simple factories are not a design pattern, but rather a programming optimization habit for decoupling object creation from client programs

Recruitment does not give up, continue to ask, "Can there be a way to optimize the process of creating objects, it still does not meet the open and close principle! And the client invocation method is very elegant, in case the parameters are misspelled inadvertently, it will collapse directly, this trouble should not be transferred to the client is not it?"

The gyro was stunned and stared at the money for a long time, as if he had just learned programming that year. He was curious about everything and a little bit hygienic about the code. He said comfortably, "Well, let's try to use reflection to continue optimizing."

/**
 * Simple factory reflecting optimized cat food
 *
 * @author Cicada bathing
 */
public class SimpleCatFoodFactoryV2 {
    public static CatFood createCatFood(Class<? extends CatFood> clazz) {
        if (clazz != null) {
            try {
                return clazz.newInstance();
            } catch (Exception e) {
                throw new RuntimeException("Object does not exist");
            }
        }
        return null;

    }
}

Client code optimization is as follows

public CatFood order(Class<? extends CatFood> clazz) {

    CatFood catFood = SimpleCatFoodFactoryV2.createCatFood(clazz);

    catFood.make();

    return catFood;
}

"At this point, SimpleCatFoodFactoryV2 conforms to the open-close principle, but one of the basic principles of using reflection here is that all objects must be constructed in a consistent way. If the process of creating objects is complex and has their own characteristics, then optimizing to this step may not be the best option. Keep in mind the principle of optimizing - just the right one," added the gyro.

Recruiting money is very impressed with the optimization and explanation of the gyro. I want to practice with such a good boss and master, and also try to eat my favorite cat food in peacetime. This is simply heaven.

Cat Food Company Expansion

Days passed and the company was successful with gyroscopes and plans to set up branches throughout the country. In order to ensure the quality of service, gyroscopes want branch companies to be able to use their time-tested code.

But different branch companies need to produce different tastes of products according to local characteristics, such as Shandong shallot cat food, big sauce cat food, Hunan pepper cat food, chopped pepper cat food...

Looking for money, it's not easy! Continue to use SimpleCatFoodFactoryV2 and let companies inherit CatFood from their new cat food.

But on the other hand, as each branch's product chain becomes richer and the process of getting products varies, SimpleCatFoodFactoryV2 will have more and more responsibilities, like an omnipotent class, which is not easy to maintain.

The idea is to create a separate, simple factory for each branch, and then bind specific, simple factory objects to the PaoMaChang object so that customers can order by specifying the factory and flavor of the branch.

PaoMaChangV3 is refactored as follows

/**
 * Runway Object - Version 3
 * @author Cicada bathing
 */
public class PaoMaChangV3 {

    private ICatFoodFactory factory;

    public PaoMaChangV3(ICatFoodFactory factory) {
        this.factory = factory;
    }

    public CatFood order(String flavor) {

        CatFood catFood = factory.create(flavor);

        catFood.make();

        return catFood;
    }
}

Make the factory itself an abstraction and create the ICatFoodFactory interface

public interface ICatFoodFactory {
    CatFood create(String flavor);
}

Factory code for each branch

/**
 * Simple Factory of Shandong Branch
 *
 * @author Cicada bathing
 */
public class ShanDongSimpleCatFoodFactory implements ICatFoodFactory {
    CatFood catFood;

    @Override
    public CatFood create(String flavor) {
        if ("congxiang".equals(flavor)) {
            catFood = new CongXiangCatFood();
        } else if ("dajiang".equals(flavor)) {
            catFood = new DaJiangCatFood();
        } else {
            throw new RuntimeException("No cat food found for this taste");
        }

        return catFood;
    }
}

/**
 * Simple Factory of Hunan Branch
 *
 * @author Cicada bathing
 */
public class HuNanSimpleCatFoodFactory implements ICatFoodFactory {
    CatFood catFood;

    @Override
    public CatFood create(String flavor) {
        if ("duojiao".equals(flavor)) {
            catFood = new DuoJiaoCatFood();
        } else if ("mala".equals(flavor)) {
            catFood = new MaLaCatFood();
        } else {
            throw new RuntimeException("No cat food found for this taste");
        }

        return catFood;
    }
}

The cat food codes for each taste are as follows

/**
 * Big sauce cat food
 */
public class DaJiangCatFood extends CatFood {
    public DaJiangCatFood() {
        this.flavor = "dajiang";
    }

    @Override
    public void make() {
        System.out.println("Making [Big Sauce] Cat Food");
    }
}

/**
 * Chives and spices cat food
 */
public class CongXiangCatFood extends CatFood {
    public CongXiangCatFood() {
        this.flavor = "congxiang";
    }

    @Override
    public void make() {
        System.out.println("Cat food is being prepared with chives");
    }
}

/**
 * Chopped pepper and cat food
 */
public class DuoJiaoCatFood extends CatFood {
    public DuoJiaoCatFood() {
        this.flavor = "duojiao";
    }

    @Override
    public void make() {
        System.out.println("Preparing [Chopped Pepper] Cat Food");
    }
}

/**
 * Spicy Cat Food
 */
public class MaLaCatFood extends CatFood {
    public MaLaCatFood() {
        this.flavor = "mala";
    }

    @Override
    public void make() {
        System.out.println("Making spicy cat food");
    }
}

The UML diagram for the product class is

This is what happens when a customer orders "Shaved Pepper Cat Food" from Hunan Branch

public static void main(String[] args) {
    HuNanSimpleCatFoodFactory huNanSimpleCatFoodFactory = new HuNanSimpleCatFoodFactory();
    PaoMaChangV3 paoMaChang = new PaoMaChangV3(huNanSimpleCatFoodFactory);
    //Order chopped pepper and cat food
    paoMaChang.order("duojiao");
}

At this point, the code has been reconstructed and the carefully checked system has finally come online. Local branch companies use this system to start their own business in an orderly manner, and the situation is great!

Later one day, Recruitment received a phone call from the gyro, and let him go to the gyro's office in a hurry. Recruitment was trembling all the way, wondering if there was something wrong with his code. When he comes to the office, the gyro greets him with money and sits next to him, pointing at the full screen of code and saying: "Don't worry, there are no bug s in your code so far. By creating your own simple factory for each branch and injecting the simple factory object as a parameter into the PaoMaChang class, you can see that you've been working hard on your code lately. I just found a potential hazard when I reviewed the code for each branch." Say, open the code of a branch to recruit money.

/**
 * Hunan Running Yard Branch
 * @author Cicada bathing
 */
public class HuNanPaoMaChangV3 {

    private ICatFoodFactory factory;

    public HuNanPaoMaChangV3(ICatFoodFactory factory) {
        this.factory = factory;
    }

    public CatFood order(String flavor) {

        CatFood catFood = factory.create(flavor);

        catFood.make();
        
        //Hunan Branch added its own "packaging" logic
        catFood.pack();

        return catFood;
    }

}

Zhao Cai saw that the technical staff of Hunan Branch added a pack() packaging method in order() method without authorization, and the gyro continued to say: "Regardless of whether this logic is added correctly or not, it is risky for a branch company to be able to change our core code. You need to find a way to not only let each branch create products freely, but also ensure that our core functions are not changed. The core logic can only be determined by us."

"This is really a problem. At present, the order logic of each branch is defined by itself. We need to provide a real"framework"for them to follow our standards for business logic."

"That's right!" The gyro looked comfortably at the money.

"In that case, I can change our PaoMaChangV3 to abstract, named PaoMaChangV4, let subsidiaries inherit this class, and then add final keyword to order() to prohibit subclasses from overwriting, so they can only use our order logic," he said as he thought about recruiting money.

"Then how do you want the subsidiary to have the freedom to control all kinds of products?" asked the gyro.

"I have recently studied polymorphism and inheritance, and the create() method of the order() method delays the execution of the method to a subclass without any specific action." Say that, the recruiting immediately wrote the following code.

/**
 * Runway Object - Version 4
 * @author Cicada bathing
 */
public abstract class PaoMaChangV4 {

    public final CatFood order(String flavor) {

        CatFood catFood = create(flavor);

        catFood.make();

        return catFood;
    }
    
    //This method requires subclass inheritance
    public abstract CatFood create(String flavor);

}

"The order() method only calls the create() method, and the subclass created by the subsidiary company is responsible for the specific implementation of the create() method. The corresponding codes of Hunan branch and Shandong branch are as follows". The recruiting of money is then explained.

/**
 * Hunan Running Yard Branch V4
 *
 * @author Cicada bathing
 */
public class HuNanPaoMaChangV4 extends PaoMaChangV4 {

    @Override
    public CatFood create(String flavor) {
        CatFood catFood;
        if ("duojiao".equals(flavor)) {
            catFood = new DuoJiaoCatFood();
        } else if ("mala".equals(flavor)) {
            catFood = new MaLaCatFood();
        } else {
            throw new RuntimeException("No cat food found for this taste");
        }

        return catFood;
    }

}

/**
 * Shandong Running Yard Branch V4
 *
 * @author Cicada bathing
 */
public class ShanDongPaoMaChangV4 extends PaoMaChangV4 {

    @Override
    public CatFood create(String flavor) {
        CatFood catFood;
        if ("congxiang".equals(flavor)) {
            catFood = new CongXiangCatFood();
        } else if ("dajiang".equals(flavor)) {
            catFood = new DaJiangCatFood();
        } else {
            throw new RuntimeException("No cat food found for this taste");
        }

        return catFood;
    }
}

The corresponding UML diagram is

The order form for the end customer has changed

//Order chopped pepper and cat food
public static void main(String[] args) {
  	//Customers first need an object from Hunan Branch
    PaoMaChangV4 huNanPaoMaChangV4 = new HuNanPaoMaChangV4();
  	//Then place an order
    huNanPaoMaChangV4.order("duojiao");
}

"It looks like you're really impressed. The idea you've just summarized is really a well-known factory method model." The gyro laughed with satisfaction. "Factory method mode achieves the purpose of encapsulating objects in the process of creating them by letting subclasses decide what they are supposed to create."

Factory method mode: Defines an interface for creating objects, worries about subclasses deciding which class to instantiate, and defers class instantiation to subclasses.

"Ah!" was amazed at the recruitment, but I didn't expect to stud out the factory method mode by mistake. "I don't really think so much, just want to solve the current problems and adapt to future changes."

"I know, I'm afraid you need to be able to summarize now when to use the simple factory model and when to use the factory method model without saying it accurately. Design models are just methodologies that have been developed by previous people to continuously optimize their own code. Don't stick to the name of your optimization method, or simply forget the terms I just mentioned, and use them at the right time. The right way to solve the problem is the most important! Don't learn design patterns, just feel like you're holding a hammer in your hand and see what's a nail."

"I understand Master! But I hear there is another design mode about the factory. Would you like to tell me more about it?"

Factory for Cat Food Raw Materials

"There is also an abstract factory model, which is too simple for you if you understand the step-by-step optimization of our system. Let's give you an example using our company scenario."

"If we want to further control the raw materials for cat food production in our branch companies and avoid the uneven quality of the raw materials in each branch. The main raw materials for cat food production are the same. Meat, oats, fruits and vegetables, taurine and so on are required, but different branch companies have different production processes for raw materials, so the abstract factory is suitable for this scenario."

"So how do you design it?"

"It's simple. We can create a raw material factory for each branch. This raw material factory must meet the standards we have set, like this one." We recruited money and wrote a pseudo code.

public interface CatFoodIngredientAbstractFactory {
    // Meat production
    Meat createMeat();

    // Oat Production
    Oats createOats();

    // Fruit and Vegetable Production
    FruitsAndVegetables createFruitsAndVegetables();

    // Production of Taurine
    Taurine createTaurine();
}

"Each branch's own raw material factory must implement CatFoodIngredientFactory to achieve each creation method, taking Shandong branch as an example."

/**
 * Cat Grain Raw Material Factory, Shandong Branch
 *
 * @author Cicada bathing
 */
public class ShanDongCatFoodIngredientFactory implements CatFoodIngredientAbstractFactory {
    @Override
    public Meat createMeat() {
        return new ShanDongMeat();
    }

    @Override
    public Oats createOats() {
        return new ShanDongOats();
    }

    @Override
    public FruitsAndVegetables createFruitsAndVegetables() {
        return new ShanDongFruitsAndVegetables();
    }

    @Override
    public Taurine createTaurine() {
        return new ShanDongTaurine();
    }
}

Note: There are many classes in the code that are not implemented and you just need to understand what they mean.

Caicai continued to ask: "How can we now link the raw material factories of each branch with cat food?"

"Don't worry, in order to better interpret Abstract factories, we need to change our CatFood class first. This is just a change to explain the abstract factory model, and it has nothing to do with our own business logic."

/**
 * An abstract class of cat food from which all specific flavors of cat food must be inherited
 *
 * @author Cicada bathing
 */
public abstract class CatFoodV2 {
    public String flavor;

    public Meat meat;
    public Oats oats;
    public FruitsAndVegetables fruitsAndVegetables;
    public Taurine taurine;


    public abstract void make();
}

"The next priority is how to create specific tastes of cat food. How do you feel about associating cat food with raw material factories?"

"You can add an object of a material factory to the subclass, and you can select a material factory to initialize the object of cat food products. This decouples the cat food from the specific material. Cat food just needs to know how to make it, like this."

/**
 * Big sauce cat food
 */
public class DaJiangCatFoodV2 extends CatFoodV2 {

    private CatFoodIngredientFactory catFoodIngredientFactory;

    public DaJiangCatFoodV2(CatFoodIngredientFactory catFoodIngredientFactory) {
        this.flavor = "dajiang";
        this.catFoodIngredientFactory = catFoodIngredientFactory;
    }

    @Override
    public void make() {
        System.out.println("Using ingredients:");
        System.out.println("Meat:" + catFoodIngredientFactory.createMeat());
        System.out.println("Oatmeal:" + catFoodIngredientFactory.createOats());
        System.out.println("Fruits and vegetables:" + catFoodIngredientFactory.createFruitsAndVegetables());
        System.out.println("Taurine:" + catFoodIngredientFactory.createTaurine());
        System.out.println("Making [Big Sauce] Cat Food");
    }
}

"The children are teachable," said the gyro comfortably. "You already know the essence of object-oriented, so you can write out the code of the branch. Give it a try."

Recruitment quickly wrote the code.

/**
 * Runway Object - Version 5
 *
 * @author Cicada bathing
 */
public abstract class PaoMaChangV5 {

    public final CatFoodV2 order(String flavor) {

        CatFoodV2 catFood = create(flavor);

        catFood.make();

        return catFood;
    }

    //This method requires subclass inheritance
    public abstract CatFoodV2 create(String flavor);

}

/**
 * Shandong Running Yard Branch V5
 *
 * @author Cicada bathing
 */
public class ShanDongPaoMaChangV5 extends PaoMaChangV5 {
    //Shandong Branch uses raw materials from Shandong Raw Material Factory
    CatFoodIngredientFactory catFoodIngredientFactory = new ShanDongCatFoodIngredientFactory();

    @Override
    public CatFoodV2 create(String flavor) {
        CatFoodV2 catFood;
        if ("congxiang".equals(flavor)) {
            catFood = new CongXiangCatFoodV2(catFoodIngredientFactory);
        } else if ("dajiang".equals(flavor)) {
            catFood = new DaJiangCatFoodV2(catFoodIngredientFactory);
        } else {
            throw new RuntimeException("No cat food found for this taste");
        }

        return catFood;
    }
}

"So far, we've transformed our business with the abstract factory model, and the logic of customer orders hasn't changed. For completeness, we've defined the abstract factory," says the gyro.

Abstract Factory Mode: Provides an interface for creating a family of related or dependent objects without explicitly specifying specific classes.

"You let me write for myself that I feel I can write for myself," said the profiteer depressed, "I'm overwhelmed with all the explanations you've given me!"

"Hahahahahaha, there are three realms of learning, the first one is to see mountains and water; the second one is to see mountains not mountains but water not water; the third one is to see mountains still mountains and water still water. You are now in the first transition to the second one," the gyro amused.

"We'll go through the process of upgrading our system from scratch to help you understand it."

summary

"At the beginning, our company only produced one product - Fish civet cat food. At this time, you can write business logic directly for the product creation class FishCatFood without any optimization."

"Later, the company produced two other products in succession. Due to the relevance of each product, you created the CatFood Abstract class. Each product you produce then needs to inherit this class, and then make the product according to the user's taste in the order() method. However, as the company grows, the product may change over and over again (dramatically increase or drop shelves), and order() The method no longer meets the open and close principle, so we pull the code that created the object out of SimpleCatFoodFactory for unified management, which is a simple factory."

"Later the company established subsidiaries in other provinces, each of which had its own products. To avoid SimpleCatFoodFactory becoming a universal factory, we created separate, simple factories for each of the subsidiaries and created product objects according to our requirements."

"We don't want subsidiaries to be able to modify the logic in order(), so we try to create a''framework', which forces subsidiaries to use our order logic while guaranteeing them the flexibility to freely create products. So we use the abstract create() method in the PaoMaChangV4 abstract class and we will implement create() Behaviors are deferred to subclasses and the underlying framework is established in the parent class. This step makes order() independent of the specific class, in other words, decoupling. When the order() method calls the create() method, the subclass (subsidiary object) of PaoMaChangV4 will be responsible for creating the real product. This is the factory method pattern."

"Finally, we want to make sure that the control of raw materials for each product of each subsidiary defines a family of raw materials. There is an implicit assumption that the raw materials used for each product are the same, but the difference is the way of production."

"We created the Material Factory CatFoodIngredientAbstractFactory interface, which defines the interface to create all the ingredients, and look at the code again."

public interface CatFoodIngredientAbstractFactory {
    // Meat production
    Meat createMeat();

    // Oat Production
    Oats createOats();

    // Fruit and Vegetable Production
    FruitsAndVegetables createFruitsAndVegetables();

    // Production of Taurine
    Taurine createTaurine();
}

"Next we created a subclass for each branch that implements the CatFoodIngredientAbstractFactory interface to implement each creation method. To better explain the abstract factory model, we slightly modified the cat food class to get CatFoodV2. All the specific products still inherit from CatFoodV2, and each product needs a different one from the constructor The raw material factory, the catFoodIngredientFactory variable injected into the object, and the make() method in CatFoodV2 use the raw material created by the factory."

"Finally, summarize the scenario for using the abstract factory model. When you need to use a family of ingredients to create products you want to make, you can consider using the abstract factory model."

I am a cicada bathing wind, a narrator who lets you indulge in technology. Welcome to leave a message!

Posted by Joshua4550 on Wed, 03 Nov 2021 09:16:38 -0700