【问题标题】:Design Pattern for different users针对不同用户的设计模式
【发布时间】:2021-03-30 20:23:48
【问题描述】:

我的应用中有 2 种不同类型的用户:LoggedInUserGuestUser 它们都具有相同的功能,例如createNewOrdercancelOrder(实现应该不同),但LoggedInUser 可以做更多的事情,例如WriteReviewFavouriteProduct 等。

我想为这些类型的用户设计一个设计模式,这样我就不会检查我的应用程序目前拥有哪种类型的用户,而我只是在不知道用户的实际类型的情况下调用方法。

甚至有可能做到这一点吗?哪种设计模式最合适?

到目前为止,我所做的是创建一个名为 User 的抽象类,它具有类似的功能:

abstract class User{
    void createOrder()
    void cancelOrder()
}

还有一个LoggedInUser 的类和一个GuestUser 的类:

class LoggedInUser extends User {
    //some instance variables

    void favouriteProduct(String id) {
      //...
    }

    void writeReview(Review review) {
        //...
    }
  
    @override
    void cancelOrder(){
         //...
    }
  
    @override
    void cancelOrder(){
         //...
    }
}

class GuestUser extends User {
    //some instance variables
 
    @override
    void cancelOrder(){
       //...
    }

    @override
    void cancelOrder(){
       //...
    }
}

谢谢。

【问题讨论】:

  • 点击“添加到收藏夹”时,您知道要调用哪个端点。该端点将从一开始就与 LoggedInUser 一起使用,而不是与接口一起使用。所以应该没有问题..
  • @StanislavBashkyrtsev 问题是我不想在运行时检查用户类型。默认用户类没有favouriteProdocut 方法,所以当我调用它时,除非我检查对象的类型,否则它将不起作用。我说的对吗?
  • 但是在调用与favouriteProduct 相关的任何内容的代码中,您只能使用LoggedInUsers,不会有任何GuestUsers,对吧?如果没有 - 您是否还可以添加您想到的代码。
  • 我不确定,但 策略模式 在这里看起来不错。 baeldung.com/java-strategy-pattern
  • @StanislavBashkyrtsev 是的,但是当我登录用户时,我有一个 User 类型的变量,然后我根据登录时采取的操作为其分配 GuestUserLoggedInUser屏幕。

标签: java object oop design-patterns


【解决方案1】:

如果您绝对坚决反对必须检查您拥有的用户类型,并且无论如何都希望以相同的方式对待他们(我不确定您为什么想要那样),您可以添加默认实现您的抽象 User 类中的 favouriteProductwriteReview 方法。这些默认实现将在GuestUser 实例上调用它们时使用,而您将在LoggedInUser 中覆盖它们。我认为这不是一个伟大的设计。

public class UserExample
{
    public static void main(String[] args)
    {
        User loggedInUser = new LoggedInUser();
        User guestUser = new GuestUser();

        loggedInUser.favouriteProduct("123");
        guestUser.favouriteProduct("123");

        Review review = new Review();
        review.text = "This product is amazing.";

        loggedInUser.writeReview(review);
        guestUser.writeReview(review);
    }
}

abstract class User
{
    abstract void createOrder();

    abstract void cancelOrder();

    public void favouriteProduct(String id)
    {
        System.out.printf("UserAbstract::favouriteProduct(%s)%n", id);
    }

    public void writeReview(Review review)
    {
        System.out.printf("UserAbstract::writeReview%n\t%s%n", review.text);
    }
}

class LoggedInUser extends User
{
    //some instance variables

    @Override
    public void favouriteProduct(String id)
    {
        System.out.printf("LoggedInUser::favouriteProduct(%s)%n", id);
    }

    @Override
    public void writeReview(Review review)
    {
        System.out.printf("LoggedInUser::writeReview%n\t%s%n", review.text);
    }

    @Override
    public void createOrder()
    {
        //...
    }

    @Override
    public void cancelOrder()
    {
        //...
    }
}

class GuestUser extends User
{
    //some instance variables

    @Override
    public void createOrder()
    {
        //...
    }

    @Override
    public void cancelOrder()
    {
        //...
    }
}


class Review
{
    public String text;
}

您最好将favouriteProductwriteReview 方法移至服务类。这些方法可以接受实现User 接口的实例,然后决定如何以不同的方式处理LoggedInUsersGuestUsers 的实例。这使您可以非常通用地传递用户,并且仅在绝对必要时才分支。您可以在服务方法中忽略GuestUsers,抛出异常,重定向到登录,随心所欲。 IMO 这种逻辑不属于您的业务对象。

public class UserExample2
{
    public static void main(String[] args)
    {
        LoggedInUser loggedInUser = new LoggedInUser();
        GuestUser guestUser = new GuestUser();

        UserService.favouriteProduct(loggedInUser, "123");
        UserService.favouriteProduct(guestUser, "123");

        Review review = new Review();
        review.text = "This product is amazing.";

        UserService.writeReview(loggedInUser, review);
        UserService.writeReview(guestUser, review);
    }
}

interface User
{
    void createOrder();

    void cancelOrder();
}

class LoggedInUser implements User
{
    //some instance variables

    public void createOrder()
    {
        //...
    }

    public void cancelOrder()
    {
        //...
    }
}

class GuestUser implements User
{
    //some instance variables

    public void createOrder()
    {
        //...
    }

    public void cancelOrder()
    {
        //...
    }
}

class UserService
{
    public static void favouriteProduct(LoggedInUser user, String id)
    {
        System.out.printf("UserService::favouriteProduct LoggedInUser favourites %s%n", id);
    }

    public static void favouriteProduct(GuestUser user, String id)
    {
        System.out.printf("UserService::favouriteProduct GuestUser favourites %s%n", id);
    }

    public static void writeReview(LoggedInUser user, Review review)
    {
        System.out.printf("UserService::writeReview LoggedInUser writes%n\t%s%n", review.text);
    }

    public static void writeReview(GuestUser user, Review review)
    {
        System.out.printf("UserService::writeReview GuestUser writes%n\t%s%n", review.text);
    }
}

class Review
{
    public String text;
}

[编辑]

这里有一个更详尽的例子来说明如何解决这类问题。这更清楚地说明了如何使用多态性和继承在典型的电子商务购物车场景中跨不同类型的会话实现通用功能。

import java.util.*;

public class UserExample3
{
    public static void main(String[] args)
    {
        // Set up our user with test data IRL this would be retrieved from authentication mechanism
        Address userAddress = new Address("1313 Mockingbird Lane", "London", "GB", "12345");
        User loggedInUser = new User(23, "Alice", userAddress);

        // Set up a session for our user
        UserSession userSession = new UserSession(loggedInUser);

        // Set up a guest session
        GuestSession guestSession = new GuestSession();

        // Add items - action happens in the abstract class
        userSession.addOrderItem("123", 2);
        guestSession.addOrderItem("456", 3);

        // Favourite items - action happens in the concrete classes
        userSession.favouriteProduct("123");
        guestSession.favouriteProduct("456");

        // List items via the cart service, requires instances of Session interface
        CartService.listCartItems(userSession);
        CartService.listCartItems(guestSession);

        // Write reviews via the Review service, requires instances of Session interface
        Review userReview = ReviewService.writeReview(userSession, "123", "This product is amazing");
        System.out.println(userReview);

        Review guestReview = ReviewService.writeReview(guestSession, "456", "This product is pants");
        System.out.println(guestReview);

        // Create orders via the order service, requires instances of Session interface
        Order userOrder = null;
        try
        {
            userOrder = OrderService.createOrderFromSession(userSession);
            System.out.println(userOrder);
        }
        catch (Exception e)
        {
            System.out.println(e.getMessage());
        }

        // Guest checkout requires us to collect an address (user already has address saved in their profile)
        Address guestAddress = new Address("1060 W. Addison", "Chicago", "US", "12345");
        guestSession.setAddress(guestAddress);

        Order guestOrder = null;
        try
        {
            guestOrder = OrderService.createOrderFromSession(guestSession);
            System.out.println(guestOrder);
        }
        catch (Exception e)
        {
            System.out.println(e.getMessage());
        }

        // Estimate shipping charges via cart service
        double userEstimate = CartService.estimateShipping(userSession);
        System.out.printf("User shipping estimate: %.2f%n", userEstimate);

        double guestEstimate = CartService.estimateShipping(guestSession);
        System.out.printf("Guest shipping estimate: %.2f%n", guestEstimate);

        // Convert a guest session to a user session via CartService - requires concrete classes
        UserSession newUserSession = new UserSession(loggedInUser);
        CartService.populateUserSessionCartFromGuestSession(newUserSession, guestSession);

            /*
                The guest session items are now in the user session
                and the guest session favorites have been added to the user

                Requires instances of Session interface
             */
        CartService.listCartItems(newUserSession);
        System.out.println(loggedInUser);

        // Empty the cart via the cart service
        CartService.emptyCart(newUserSession);
        CartService.listCartItems(newUserSession);

        // Cancel an order via the order service - will be null if validation failed
        if(userOrder != null)
        {
            OrderService.cancelOrder(userOrder);
            System.out.println(userOrder);
        }

        // Complete an order via the order service - will be null if validation failed
        if(guestOrder != null)
        {
            OrderService.completeOrder(guestOrder);
            System.out.println(guestOrder);
        }
    }
}

/**
 * Do cart things
 */
class CartService
{
    /**
     * Print the line items in a cart
     * <p>
     * This is an example of polymorphism - any instance that implements the
     * Session interface will work here
     *
     * @param session instance of interface Session
     */
    public static void listCartItems(Session session)
    {
        System.out.println("Order items:");
        for (Map.Entry<String, Integer> currItem : session.getOrderItems().entrySet())
        {
            System.out.printf("\t%s: %d%n", currItem.getKey(), currItem.getValue());
        }
    }

    /**
     * Populate a UserSession with data collected in a guest session
     * Used when a user begins their order while unauthenticated, the logs in prior to completing the order
     *
     * @param userSession  Instance of UserSession
     * @param guestSession Instance of GuestSession
     */
    public static void populateUserSessionCartFromGuestSession(UserSession userSession, GuestSession guestSession)
    {
        userSession.getOrderItems().putAll(guestSession.getOrderItems());

        userSession.getUser().getFavouriteProducts().addAll(guestSession.getFavouriteProducts());

        guestSession.clearOrderItems();
    }

    /**
     * Clear the items from a cart
     * <p>
     * This is an example of polymorphism - any instance that implements the
     * Session interface will work here
     *
     * @param session Instance of Session interface
     */
    public static void emptyCart(Session session)
    {
        session.clearOrderItems();
    }

    public static double estimateShipping(Session session)
    {
        Map<String, Double> perItemChargesByCountry = new HashMap<>();
        perItemChargesByCountry.put("US", 1.23);
        perItemChargesByCountry.put("GB", 1.05);

        String countryCode = session.getAddress().getCountry();
        Double perItemCharge = perItemChargesByCountry.get(countryCode);

        int numItems = 0;
        for (Map.Entry<String, Integer> currLineItem : session.getOrderItems().entrySet())
        {
            numItems += currLineItem.getValue();
        }

        return numItems * perItemCharge;
    }
}

/**
 * Do order things
 */
class OrderService
{
    /**
     * Create an order from the line items in a cart
     * <p>
     * This is an example of polymorphism - any instance that implements the
     * Session interface will work here
     * <p>
     * If the session is an instance of UserSession, data available only on
     * those instances will be set on the order record
     *
     * @param session Instance of Session interface
     * @return Order
     */
    public static Order createOrderFromSession(Session session) throws Exception
    {
        Order order = new Order();
        order.setStatus(Order.Status.PENDING);
        order.setAddress(session.getAddress());
        order.getItems().putAll(session.getOrderItems());

        if (session instanceof UserSession)
        {
            order.setUser(((UserSession) session).getUser());
        }

        if(!order.validate())
        {
            throw new Exception("Something is wrong with the order");
        }

        return order;
    }

    /**
     * Cancel an order by changing its status
     *
     * @param order Order
     */
    public static void cancelOrder(Order order)
    {
        order.setStatus(Order.Status.CANCELLED);
    }

    /**
     * Complete an order by changing its status
     *
     * @param order Order
     */
    public static void completeOrder(Order order)
    {
        order.setStatus(Order.Status.COMPLETE);
    }
}

/**
 * Do review things
 */
class ReviewService
{
    /**
     * Create a product review
     * <p>
     * This is an example of polymorphism - any instance that implements the
     * Session interface will work here
     * <p>
     * If the session is an instance of UserSession, data available only on
     * those instances will be set on the review record
     *
     * @param session   Session
     * @param productId String
     * @param text String content of review
     * @return Review
     */
    public static Review writeReview(Session session, String productId, String text)
    {
        Review review = new Review();

        if (session instanceof UserSession)
        {
            review.setUser(((UserSession) session).getUser());
        }

        review.setProductId(productId);
        review.setText(text);

        return review;
    }
}

/**
 * Describe the contract that all session classes should adhere to
 */
interface Session
{
    Address getAddress();

    Map<String, Integer> getOrderItems();

    void setOrderItems(Map<String, Integer> orderItems);

    void addOrderItem(String productId, int quantity);

    void removeOrderItem(String productId);

    void clearOrderItems();

    void favouriteProduct(String productId);
}

/**
 * Implement functionality common to all concrete session classes
 */
abstract class SessionAbstract
{
    private Map<String, Integer> orderItems = new HashMap<>();

    public Map<String, Integer> getOrderItems()
    {
        return orderItems;
    }

    public void setOrderItems(Map<String, Integer> orderItems)
    {
        this.orderItems = orderItems;
    }

    public void addOrderItem(String productId, int quantity)
    {
        if (orderItems.containsKey(productId))
        {
            quantity += orderItems.get(productId);
        }

        orderItems.put(productId, quantity);
    }

    public void removeOrderItem(String productId)
    {
        orderItems.remove(productId);
    }

    public void clearOrderItems()
    {
        orderItems.clear();
    }
}

/**
 * Concrete session class for authenticated users
 */
class UserSession extends SessionAbstract implements Session
{
    private final User user;

    public UserSession(User user)
    {
        this.user = user;
    }

    public User getUser()
    {
        return user;
    }

    @Override
    public Address getAddress()
    {
        return user.getAddress();
    }

    public void favouriteProduct(String productId)
    {
        user.addFavouriteProduct(productId);
    }
}

/**
 * Concrete session class for guest users
 *
 * We need to have an instance of Address and collection of favourite items
 * here since we don't have a user instance
 */
class GuestSession extends SessionAbstract implements Session
{
    private Address address;
    Set<String> favouriteProducts = new TreeSet<>();

    public Address getAddress()
    {
        return address;
    }

    public void setAddress(Address address)
    {
        this.address = address;
    }

    public Set<String> getFavouriteProducts()
    {
        return favouriteProducts;
    }

    public void favouriteProduct(String productId)
    {
        favouriteProducts.add(productId);
    }
}

class Address
{
    String street;
    String city;
    String country;
    String postCode;

    public Address(String street, String city, String country, String postCode)
    {
        this.street = street;
        this.city = city;
        this.country = country;
        this.postCode = postCode;
    }

    public String getStreet()
    {
        return street;
    }

    public void setStreet(String street)
    {
        this.street = street;
    }

    public String getCity()
    {
        return city;
    }

    public void setCity(String city)
    {
        this.city = city;
    }

    public String getCountry()
    {
        return country;
    }

    public void setCountry(String country)
    {
        this.country = country;
    }

    public String getPostCode()
    {
        return postCode;
    }

    public void setPostCode(String postCode)
    {
        this.postCode = postCode;
    }

    @Override
    public String toString()
    {
        return "Address{" + "street='" + street + '\'' + ", city='" + city + '\'' + ", country='" + country + '\'' + ", postCode='" + postCode + '\'' + '}';
    }
}

class User
{
    Integer id;
    String name;
    Address address;
    Set<String> favouriteProducts = new TreeSet<>();

    public User(Integer id, String name, Address address)
    {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    public Integer getId()
    {
        return id;
    }

    public void setId(Integer id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public Address getAddress()
    {
        return address;
    }

    public void setAddress(Address address)
    {
        this.address = address;
    }

    public Set<String> getFavouriteProducts()
    {
        return favouriteProducts;
    }

    public void setFavouriteProducts(Set<String> favouriteProducts)
    {
        this.favouriteProducts = favouriteProducts;
    }

    public void addFavouriteProduct(String productId)
    {
        favouriteProducts.add(productId);
    }

    @Override
    public String toString()
    {
        return "User{" + "id=" + id + ", name='" + name + '\'' + ", address=" + address + ", favouriteProducts=" + favouriteProducts + '}';
    }
}

class Order
{
    public enum Status
    {
        PENDING, CANCELLED, COMPLETE
    }

    private Status status;
    private User user;
    private Address address;
    private Map<String, Integer> items = new HashMap<>();

    public User getUser()
    {
        return user;
    }

    public void setUser(User user)
    {
        this.user = user;
    }

    public Address getAddress()
    {
        return address;
    }

    public void setAddress(Address address)
    {
        this.address = address;
    }

    public Map<String, Integer> getItems()
    {
        return items;
    }

    public void setItems(Map<String, Integer> items)
    {
        this.items = items;
    }

    public Status getStatus()
    {
        return status;
    }

    public void setStatus(Status status)
    {
        this.status = status;
    }

    public boolean validate()
    {
        boolean valid = true;

        if(items.isEmpty())
        {
            valid = false;
        }
        else if(address == null)
        {
            valid = false;
        }
        else if(status == null)
        {
            valid = false;
        }

        return valid;
    }

    @Override
    public String toString()
    {
        return "Order{" + "status=" + status + ", user=" + user + ", address=" + address + ", items=" + items + '}';
    }
}

class Review
{
    private String productId;
    private String text;
    private User user;

    public String getProductId()
    {
        return productId;
    }

    public void setProductId(String productId)
    {
        this.productId = productId;
    }

    public String getText()
    {
        return text;
    }

    public void setText(String text)
    {
        this.text = text;
    }

    public User getUser()
    {
        return user;
    }

    public void setUser(User user)
    {
        this.user = user;
    }

    @Override
    public String toString()
    {
        String name = (user != null) ? user.getName() : "Anonymous Coward";

        return "Review{" + "productId='" + productId + '\'' + ", text='" + text + '\'' + ", user=" + name + '}';
    }
}

【讨论】:

  • 我需要的是不检查代码中的用户类型,并有一个 User 实例,它根据其类型采取不同的行动。我不认为我想要什么是可能的,因为两个用户都有一些差异。如果我采用您的第二种解决方案,我仍然需要在调用方法之前检查用户类型。我并不是说每当我想做某事时这样做是不好的,但我认为必须有一种方法可以在不检查确切类型的情况下做到这一点。但第二个解决方案对我来说真的很好。谢谢
  • 您实际上不必使用其中任何一个手动检查用户类型。我的意思是,你必须在某些时候以不同的方式对待它们,因为它们不同的,但这并不是一件坏事。第二个示例是“更正确”的 OOP 方法,您正在利用多态性来弄清楚如何处理不同的实例,而不是构建手动逻辑。我构建了很多个这样的系统,#2 是我经常使用的一种模式。
  • 我在想的是我必须有 2 个变量,一个是 LoggedInUser,一个是 GuestUser。如果用户登录,GuestUser 将为空。如果用户继续作为访客,则 LoggedInUser 将为空。然后在调用 UserServices 的任何方法之前,我必须检查哪个为空,哪个不为空。我对吗?这就是为什么我认为这是一个问题。我可能完全错了:)
  • 哦,不,我不会那样做。如果您打算使用不同的具体类来实现通用接口或扩展通用抽象类,那么您有一个用户实例,可以是 GuestUser 或 LoggedInUser,然后根据需要对它们进行不同的处理。另一方面,你可以走一条完全不同的路线,拥有一个会话管理对象,带有一个用户是否登录的标志,如果是,你可以获得一个 LoggedInUser 的实例,如果没有,就不要不要做“用户的事情”。这限制了您可以对未登录用户执行的操作,但这很简单。
  • 我已经更新了示例以显示多态性 - 所有方法都接受 User 的实例 - createOrder 和 cancelOrder 是很好的示例,因为它们不关心具体类何时存在,它们只对合约进行操作在界面中描述。
猜你喜欢
  • 2018-07-10
  • 1970-01-01
  • 1970-01-01
  • 2019-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多