1

我有一个现有的银行应用程序类,如下所示。银行账户可以是 SavingsBankAccount 或 FixedBankAccount。有一个称为 IssueLumpSumInterest 的操作。对于 FixedBankAccount,仅当帐户所有者没有其他帐户时才需要更新余额。

这要求 FixedBankAccount 对象了解帐户所有者的其他帐户。如何通过遵循SOLID /DDD/GRASP/Information Expert 模式来做到这一点?

namespace ApplicationServiceForBank
{

public class BankAccountService
{
    RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository;
    ApplicationServiceForBank.IBankAccountFactory bankFactory;

    public BankAccountService(RepositoryLayer.IRepository<RepositoryLayer.BankAccount> repo, IBankAccountFactory bankFact)
    {
        accountRepository = repo;
        bankFactory = bankFact;
    }

    public void IssueLumpSumInterest(int acccountID)
    {
        RepositoryLayer.BankAccount oneOfRepositroyAccounts = accountRepository.FindByID(p => p.BankAccountID == acccountID);

        int ownerID = (int) oneOfRepositroyAccounts.AccountOwnerID;
        IEnumerable<RepositoryLayer.BankAccount> accountsForUser = accountRepository.FindAll(p => p.BankUser.UserID == ownerID);

        DomainObjectsForBank.IBankAccount domainBankAccountObj = bankFactory.CreateAccount(oneOfRepositroyAccounts);

        if (domainBankAccountObj != null)
        {
            domainBankAccountObj.BankAccountID = oneOfRepositroyAccounts.BankAccountID;
            domainBankAccountObj.AddInterest();

            this.accountRepository.UpdateChangesByAttach(oneOfRepositroyAccounts);
            //oneOfRepositroyAccounts.Balance = domainBankAccountObj.Balance;
            this.accountRepository.SubmitChanges();
        }
    }
}

public interface IBankAccountFactory
{
    DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount);
}

public class MySimpleBankAccountFactory : IBankAccountFactory
{
    public DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount)
    {
        DomainObjectsForBank.IBankAccount acc = null;

        if (String.Equals(repositroyAccount.AccountType, "Fixed"))
        {
            acc = new DomainObjectsForBank.FixedBankAccount();
        }

        if (String.Equals(repositroyAccount.AccountType, "Savings"))
        {
            //acc = new DomainObjectsForBank.SavingsBankAccount();
        }

        return acc;
    }
}

}

namespace DomainObjectsForBank
{

public interface IBankAccount
{
    int BankAccountID { get; set; }
    double Balance { get; set; }
    string AccountStatus { get; set; }
    void FreezeAccount();
    void AddInterest();
}

public class FixedBankAccount : IBankAccount
{
    public int BankAccountID { get; set; }
    public string AccountStatus { get; set; }
    public double Balance { get; set; }

    public void FreezeAccount()
    {
        AccountStatus = "Frozen";
    }

    public void AddInterest()
    {
        //TO DO: Balance need to be updated only if the person has no other accounts.
        Balance = Balance + (Balance * 0.1);
    }
}

}

阅读

  1. 将组合用于“是 - 一个”关系的问题

  2. 实现业务逻辑 (LINQ to SQL) http://msdn.microsoft.com/en-us/library/bb882671.aspx

  3. 构建 LINQ to SQL 应用程序

  4. 使用 LINQ to SQL 探索 N 层架构 http://randolphcabral.wordpress.com/2008/05/08/exploring-n-tier-architecture-with-linq-to-sql-part-3-of-n/

  5. DTO(linq2sql)和类对象之间的混淆!

  6. 领域驱动设计 (Linq to SQL) - 如何删除聚合的一部分?

4

4 回答 4

2

通过阅读您的要求,我将这样做:

//Application Service - consumed by UI
public class AccountService : IAccountService
{
    private readonly IAccountRepository _accountRepository;
    private readonly ICustomerRepository _customerRepository;

    public ApplicationService(IAccountRepository accountRepository, ICustomerRepository customerRepository)
    {
        _accountRepository = accountRepository;
        _customerRepository = customerRepository;
    }

    public void IssueLumpSumInterestToAccount(Guid accountId)
    {
        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
            Account account = _accountRepository.GetById(accountId);
            Customer customer = _customerRepository.GetById(account.CustomerId);

            account.IssueLumpSumOfInterest(customer);

            _accountRepository.Save(account);
        }
    }
}

public class Customer
{
    private List<Guid> _accountIds;

    public IEnumerable<Guid> AccountIds
    {
        get { return _accountIds.AsReadOnly();}
    }
}

public abstract class Account
{
    public abstract void IssueLumpSumOfInterest(Customer customer);
}

public class FixedAccount : Account
{
    public override void  IssueLumpSumOfInterest(Customer customer)
    {
        if (customer.AccountIds.Any(id => id != this._accountId))
            throw new Exception("Lump Sum cannot be issued to fixed accounts where the customer has other accounts");

        //Code to issue interest here
    }
}   

public class SavingsAccount : Account
{
    public override void  IssueLumpSumOfInterest(Customer customer)
    {
        //Code to issue interest here
    }
}
  1. Account 聚合上的IssueLumpSumOfInterest方法需要 Customer 聚合来帮助决定是否应该发放利息。
  2. 客户聚合包含帐户 ID 列表 -不是帐户聚合列表。
  3. 基类“Account”有一个多态方法——FixedAccount 检查客户是否有任何其他账户——SavingsAccount 不做这个检查。
于 2012-06-29T08:38:46.853 回答
2

我注意到的第一件事是银行账户工厂的不当使用。存储库应该使用工厂来根据从数据存储中检索到的数据创建实例。因此,您对 accountRepository.FindByID 的调用将返回 FixedBankAccount 或 SavingsBankAccount 对象,具体取决于从数据存储返回的 AccountType。

如果利息仅适用于 FixedBankAccount 实例,那么您可以执行类型检查以确保您使用正确的帐户类型。

public void IssueLumpSumInterest(int accountId)
{
    var account = _accountRepository.FindById(accountId) as FixedBankAccount;

    if (account == null)
    {
        throw new InvalidOperationException("Cannot add interest to Savings account.");
    }

    var ownerId = account.OwnerId;

    if (_accountRepository.Any(a => (a.BankUser.UserId == ownerId) && (a.AccountId != accountId)))
    {
        throw new InvalidOperationException("Cannot add interest when user own multiple accounts.");
    }

    account.AddInterest();

    // Persist the changes
}

注意: FindById 应该只接受 ID 参数而不是 lambda/Func。您已经通过名称“FindById”指明了搜索的执行方式。将“accountId”值与 BankAccountId 属性进行比较的事实是隐藏在方法中的实现细节。如果您想要使用 lambda 的通用方法,请将方法命名为“FindBy”。

如果所有实现都不支持该行为,我也不会将 AddInterest 放在 IBankAccount 接口上。考虑一个公开 AddInterest 方法的单独 IInterestEarningBankAccount 接口。如果您将来添加其他支持此行为的帐户类型,我还将考虑在上述代码中使用该接口而不是 FixedBankAccount 以使代码更易于维护和扩展。

于 2012-06-28T13:21:34.257 回答
1

2 min scan answer..

  • Not sure why there is a need for 2 representations of a BankAccount RepositoryLayer.BankAccount and DomainObjectsForBank.IBankAccount. Hide the persistence layer coupled one.. deal with just the domain object in the service.
  • Do not pass/return Nulls - I think is good advice.
  • The finder methods look like the LINQ methods which select items from a list of collection. Your methods look like they want to get the first match and exit..in which case your parameters can be simple primitives (Ids) vs lambdas.

The general idea seems right. The service encapsulates the logic for this transaction - not the domain objects. If this changes, only one place to update.

public void IssueLumpSumInterest(int acccountID)
{
    var customerId = accountRepository.GetAccount(accountId).CustomerId;

    var accounts = accountRepository.GetAccountsForCustomer(customerId);
    if ((accounts.First() is FixedAccount) && accounts.Count() == 1)
    {
       // update interest    
    }
}
于 2012-06-29T04:34:34.523 回答
0

让我觉得奇怪的事情:

  • IBankAccount有一个方法FreezeAccount,但我认为所有帐户都会有非常相似的行为?也许有一个BankAccount实现某些接口的类是有保证的?
  • AccountStatus应该是一个枚举?如果一个帐户是“Forzen”,会发生什么?
于 2012-06-28T14:21:21.020 回答