如何优雅的设计购物车

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何优雅的设计购物车相关的知识,希望对你有一定的参考价值。

转载请注明出处,感谢您的阅读。 http://www.cnblogs.com/HeroWong/

 

要点:

1. 购物车通常分为登录和不登录,登录存储在数据库中,未登录存储到Session/Cookie中。

2. Mvc中有个很好的特性:自定义模型绑定,开发者可以在Action方法的参数中添加一个复杂对象,由Mvc自动装配上这个对象。通过这个我们就能轻易的运用多态,屏蔽SessionCart和DbCart对象之间的差异。

3. “添加商品”和“删除商品”两个看似简单的方法,如何优雅的设计?

 

Part 1,一个基本的购物车(对购物车有自己独特的见解可以直接看Part2了。Part1会有很多冗余代码,Part2是进行重构。)

 

CartLine实体

public class CartLine
{
    public Guid Id { get; set; }
    public int GoodsId { get; set; }
    public int Quantity { get; set; }
    public int UserId { get; set; }
    public DateTime AddTime { get; set; }

    public Goods Goods { get; set; }

    public CartLine()
    {
        Id = Guid.NewGuid();
        AddTime = DateTime.Now;
    }
}

 

购物车抽象类

public abstract class Cart
{
    public abstract void Add(Goods goods, int quantity);
    public abstract void Remove(Goods goods, int quantity);
    public abstract void RemoveLine(int goodsId);
}

 

SessionCart

public class SessionCart : Cart
{
    readonly List<CartLine> lines;
    public SessionCart(List<CartLine> lines)
    {
        this.lines = lines;
    }

    public override void Add(Goods goods, int quantity)
    {
        if (quantity < 1)
        {
            throw new ArgumentException("quantity值不能小于\\"1\\"", "quantity");
        }
        var line = lines.SingleOrDefault(c => c.GoodsId == goods.Id);
        if (line == null)
        {
            lines.Add(new CartLine
            {
                GoodsId = goods.Id,
                Goods = goods,
                Quantity = quantity
            });
        }
        else
        {
            line.Quantity += quantity;
        }
    }

    public override void Remove(Goods goods, int quantity)
    {
        if (quantity < 1)
        {
            throw new ArgumentException("quantity值不能小于\\"1\\"", "quantity");
        }
        var line = lines.SingleOrDefault(c => c.GoodsId == goods.Id);
        if (line != null)
        {
            if (line.Quantity >= quantity)
            {
                RemoveLine(quantity);
            }
            else
            {
                line.Quantity -= quantity;
            }
        }
    }

    public override void RemoveLine(int goodsId)
    {
        var line = lines.SingleOrDefault(c => c.GoodsId == goodsId);
        lines.Remove(line);
    }
}

 

DbCart

public class DbCart : Cart
{
    readonly HappyDogContext context;
    public DbCart(int userId, HappyDogContext context)
    {
        this.context = context;
        UserId = userId;
    }

    public int UserId { get; private set; }

    public override void Add(Goods goods, int quantity)
    {
        if (quantity < 1)
        {
            throw new ArgumentException("quantity值不能小于\\"1\\"", "quantity");
        }
        var line = context.CartLines.SingleOrDefault(c => c.GoodsId == goods.Id && c.UserId == UserId);
        if (line == null)
        {
            context.CartLines.Add(new CartLine
            {
                GoodsId = goods.Id,
                Quantity = quantity,
                UserId = UserId
            });
        }
        else
        {
            line.Quantity += quantity;
        }
        context.SaveChanges();
    }

    public override void Remove(Goods goods, int quantity)
    {
        if (quantity < 1)
        {
            throw new ArgumentException("quantity值不能小于\\"1\\"", "quantity");
        }
        var line = context.CartLines.SingleOrDefault(c => c.GoodsId == goods.Id && c.UserId == UserId);
        if (line != null)
        {
            if (line.Quantity >= quantity)
            {
                RemoveLine(quantity);
            }
            else
            {
                line.Quantity -= quantity;
            }
            context.SaveChanges();
        }
    }

    public override void RemoveLine(int goodsId)
    {
        var line = context.CartLines.SingleOrDefault(c => c.GoodsId == goodsId && c.UserId == UserId);
        context.CartLines.Remove(line);
    }
}

 

以上就是一个普通的,不够优雅的购物车。我们不难发现,出现了很多冗余代码

问题如下:

1.查找一个CartLine冗余

2.判断quantity值是否合理冗余

3.对于后续扩展不够友好,例如 如果再加入CookieCart,还需要实现太多方法。

 

Part 2 

重构分析:我们发现,SessionCart和DbCart里面的Add/Remove两个方法,代码太过类似,能否复用?(当然可以)

当我们发现子类有重复的代码,可以考虑把这些重复的放到父类(模板方法模式)。

重构后的Cart

public abstract class Cart
{
    public abstract void RemoveLine(int goodsId);
    public abstract CartLine this[int goodsId] { get; }


    protected abstract void RealAdd(Goods goods, int quantity);
    protected abstract void SaveChanges();

    public void Add(Goods goods, int quantity)
    {
        if (quantity < 1)
        {
            throw new ArgumentException("quantity值不能小于\\"1\\"", "quantity");
        }
        var line = this[goods.Id];
        if (line == null)
        {
            RealAdd(goods, quantity);
        }
        else
        {
            line.Quantity += quantity;
        }
        SaveChanges();
    }

    public void Remove(Goods goods, int quantity)
    {
        if (quantity < 1)
        {
            throw new ArgumentException("quantity值不能小于\\"1\\"", "quantity");
        }
        var line = this[goods.Id];
        if (line != null)
        {
            if (line.Quantity >= quantity)
            {
                RemoveLine(goods.Id);
            }
            else
            {
                line.Quantity -= quantity;
            }
            SaveChanges();
        }
    }
}

 

重构后的SessionCart

public class SessionCart : Cart
{
    readonly List<CartLine> lines;
    public SessionCart(List<CartLine> lines)
    {
        this.lines = lines;
    }

    public override CartLine this[int goodsId] => lines.SingleOrDefault(c => c.GoodsId == goodsId);

    public override void RemoveLine(int goodsId) => lines.Remove(this[goodsId]);

    protected override void RealAdd(Goods goods, int quantity)
    {
        lines.Add(new CartLine
        {
            GoodsId = goods.Id,
            Quantity = quantity
        });
    }

    protected override void SaveChanges() { }
}

 

重构后的DbCart

public class DbCart : Cart
{
    readonly HappyDogContext context;
    public DbCart(int userId, HappyDogContext context)
    {
        this.context = context;
        UserId = userId;
    }

    public int UserId { get; private set; }

    public override CartLine this[int goodsId]
    {
        get => context.CartLines.SingleOrDefault(c => c.GoodsId == goodsId && c.UserId == UserId);
    }

    protected override void RealAdd(Goods goods, int quantity)
    {
        context.CartLines.Add(new CartLine
        {
            GoodsId = goods.Id,
            Quantity = quantity,
            UserId = UserId
        });
    }

    protected override void SaveChanges() => context.SaveChanges();

    public override void RemoveLine(int goodsId)
    {
        var line = this[goodsId];
        if (line != null)
        {
            context.CartLines.Remove(line);
            context.SaveChanges();
        }
    }
}

 

最后一段是Mvc的自定义模型绑定

namespace HappyDog.Web.WebUI.Binders

{

    public class CartBinder : IModelBinder

    {

        const string sessionKey = "Cart";



        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

        {

            HappyDogContext context = new HappyDogContext();

            ICart cart;

            var identity = controllerContext.HttpContext.User.Identity;

            if (identity.IsAuthenticated)

            {

                cart = new DbCart(int.Parse(identity.Name), context);

            }

            else

            {

                cart = (SessionCart)controllerContext.HttpContext.Session[sessionKey];

                if (cart==null)

                {

                    cart = new SessionCart(context);

                    controllerContext.HttpContext.Session[sessionKey] = cart;

                }

            }

            return cart;

        }

    }

}

 

在Global中注册下上述。

调用: public ViewResult Add(Cart cart,Goods goods){  }

 

希望对大家有帮助。如果不足之处希望您能够指点。

以上是关于如何优雅的设计购物车的主要内容,如果未能解决你的问题,请参考以下文章

JVM安全退出(如何优雅的关闭java服务)

如何从 recyclerview 片段传递到另一个 recyclerview 片段

在编写RTOS代码时,如何设计一个简单优雅可拓展的任务初始化结构?

在编写RTOS代码时,如何设计一个简单优雅可拓展的任务初始化结构?

Python优雅地可视化数据

如何在片段着色器中平铺部分纹理