Posizionamento corretto dei metodi in OOP

2

Quale Classe dovrebbe contenere un metodo che ha dipendenze su più classi?

Ho codificato un esempio specifico per evitare generalizzazioni. In questo esempio abbiamo un requisito iniziale:

customers have accounts and can purchase orders, which contain items.

L'applicazione è codificata secondo i principi OOP. Poiché l'acquisto richiede un account, che è di proprietà di un cliente e il requisito è espresso in termini di un cliente che effettua un acquisto, il cliente sembra un buon posto per metterlo.

Tuttavia:

Added Requirement : An advance order can be reserved by a customer if they have enough funds and purchased at a later date. an advance order can't be purchased if it has not been reserved.

Una volta aggiunta la logica per l'ordine anticipato, il posizionamento del metodo sul cliente sembra meno sicuro. Abbiamo dovuto ridimensionare un po 'per farlo entrare e avere un metodo duplicato "Acquista" su ordine

L'acquisto del cliente è coerente con i principi OOP e il buon design? O c'è un posto migliore per metterlo, abbiamo bisogno di una nuova classe?

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace OOP
{
#region Repository Assembly
    public class OrderRepository : IOrderRepository
    {
        public void Add(Order order) { }
    }
    public class AccountRepository : IAccountRepository
    {
        public void Update(Account account) { }
    }
#endregion

#region Models Assembly
    public interface IOrderRepository
    {
        void Add(Order order);
    }

    public interface IAccountRepository
    {
        void Update(Account account);
    }

    public class Customer
    {
        public string Id { get; private set; }
        public string Name { get; private set; }
        public Account Account { get; private set; }

        public Customer(Account account)
        {
            this.Account = account;
        }

        public void Purchase(Order order)
        {
            if (Account.HasFundsAvailable(order.TotalPrice()))
            {
                Account.Debit(order.TotalPrice());
                order.Purchase();
            }
            else
            {
                throw new Exception("not enough funds in account");
            }
        }

        /// added code
        public void Purchase(AdvanceOrder order)
        {
            if (!order.IsReserved())
            {
                throw new Exception("must reserve advanced orders");
            }
            if (Account.HasFundsAvailable(order.TotalPrice()))
            {
                order.Purchase();
            }
            else
            {
                throw new Exception("not enough funds in account");
            }
        }

        /// added code
        public void Reserve(AdvanceOrder order)
        {
            if (Account.HasFundsAvailable(order.TotalPrice()))
            {
                order.Reserve();
            }
            else
            {
                throw new Exception("not enough funds in account");
            }
        }
    }

    public class Account
    {
        private IAccountRepository rep;
        public string Id { get; private set; }
        public string CustomerId { get; private set; }
        public decimal Balance { get; private set; }

        public List<Transaction> Transactions { get; private set; }

        public Account(IAccountRepository rep, decimal balance, List<Transaction> Transactions)
        {
            this.rep = rep;
            this.Balance = balance;
            this.Transactions = Transactions;
        }

        public bool HasFundsAvailable(decimal amount)
        {
            if (this.Balance >= amount)
            {
                return true;
            }
            return false;
        }

        public void Debit(decimal amount)
        {
            if (this.HasFundsAvailable(amount))
            {
                Transaction t = new Transaction(amount);
                this.Transactions.Add(t);
                this.rep.Update(this);
            }
            else
            {
                throw new Exception("not enough funds");
            }
        }
    }

    public class Transaction
    {
        public Transaction(decimal amount)
        {
            this.Amount = amount;
        }
        public string Id { get; private set; }
        public decimal Amount { get; private set; }
    }

    public class Order
    {
        protected IOrderRepository rep;
        public string Id { get; private set; }
        public string Status { get; protected set; }
        public IEnumerable<Item> Items { get; private set; }

        public Order(IOrderRepository rep, string status, List<Item> items)
        {
            this.rep = rep;
            this.Status = status;
            this.Items = items;
        }

        public decimal TotalPrice()
        {
            return this.Items.Sum(i => i.Price);
        }

        public void Purchase()
        {
            this.Status = "Purchased";
            this.rep.Add(this);
        }
    }

    /// added code
    public class AdvanceOrder : Order
    {
        public AdvanceOrder(IOrderRepository rep, string status, List<Item> items)
            : base(rep, status, items)
        {
        }
        public void Reserve()
        {
            this.Status = "Reserved";
            this.rep.Add(this);
        }
        public bool IsReserved()
        {
            return this.Status == "Reserved";
        }
    }

    public class Item
    {
        public string Id { get; private set; }
        public string Name { get; private set; }
        public decimal Price { get; private set; }

        public Item(string name, decimal price)
        {
            this.Name = name;
            this.Price = price;
        }
    }
#endregion

    [TestClass]
    public class Tester
    {
        [TestMethod]
        public void PurchaseTest()
        {
            IOrderRepository orderRep = new OrderRepository();
            Item i = new Item("test",(decimal)10.2);
            List<Item> items = new List<Item>();
            items.Add(i);
            Order o = new Order(orderRep, "new", items);

            IAccountRepository accountRep = new AccountRepository();
            Account a = new Account(accountRep, (decimal)100, new List<Transaction>());

            Customer c = new Customer(a);

            c.Purchase(o);

            Assert.AreEqual("Purchased", o.Status);
        }

        [TestMethod]
        public void ReserveTest()
        {
            IOrderRepository orderRep = new OrderRepository();
            Item i = new Item("test", (decimal)10.2);
            List<Item> items = new List<Item>();
            items.Add(i);
            AdvanceOrder o = new AdvanceOrder(orderRep, "new", items);

            IAccountRepository accountRep = new AccountRepository();
            Account a = new Account(accountRep, (decimal)100, new List<Transaction>());

            Customer c = new Customer(a);

            c.Reserve(o);

            Assert.AreEqual("Reserved", o.Status);

            c.Purchase(o);

            Assert.AreEqual("Purchased", o.Status);
        }
    }
}
    
posta Ewan 12.05.2015 - 14:12
fonte

1 risposta

1

Is having Purchase on Customer consistent with OOP principles and good design? Or is there a better place to put it, do we need a new class?

No, non lo è, e sì hai bisogno di una nuova classe. Un acquisto indica che ci sono dati da persistere e che l'oggetto Cliente non deve conoscere la persistenza dei dati relativi a un ordine o persino a ciò che viene ordinato.

In genere si dispone di un oggetto servizio che sa come effettuare un ordine, quindi il codice diventa:

orderService.placeOrder(Customer customer, Shopping cart cart);
    
risposta data 15.05.2015 - 11:53
fonte

Leggi altre domande sui tag