Best way to work with a banking type class

Solarion

Honorary Master
Joined
Nov 14, 2012
Messages
28,087
Reaction score
17,859
I guess there are several ways to do this however I'm a little lost as to the most effective approach.

I have two classes lets call them SavingsAccountHelper and CurrentAccountHelper.

This is the interface for both.

public interface IAccountHelper
{
void Deposit(string accountId, decimal amountToDeposit);
void OpenAccount(string accountId);
void Withdraw(string accountId, decimal amountToWithdraw);
}

Additionally there are two classes, Savings and Current but lets work with Savings account for now which has the following properties;

public string accountId{ get; set; }
public string CustomerNumber { get; set; }
public decimal TotalBalance { get; set; }

I have put these two classes together already, performing all of those functions. My confusion is this, say you instantiate one of these classes:

SavingsAccountHelper accounthelper = new SavingsAccountHelper();

Question: What is the best way to perform the a SavingsAccountHelper functions against an account and get the new values out again? What I did initially was pass an account reference into the constructor:

SavingsAccount account = new SavingsAccount(1,DF445454,24565);
SavingsAccountHelper accounthelper = new SavingsAccountHelper(account);

The problem I am having with this approach, or not so much a problem, but it feels wrong, is getting the values out again. If someone deposits 12500 into the account, what is the best way to retrieve the new balance?

accounthelper.Deposit(accountId,12500)
 
Last edited:
account.TotalBalance ?

I'll stick with that then. That's what I've been doing yeah.

that IAccountHelper seems odd

I fixed it, got my p's and q's mixed up so to speak :D

Edit: Sorry if it's a stupid question, been having a slow day, think the heat is finally getting to me.
 
I'll stick with that then. That's what I've been doing yeah.



I fixed it, got my p's and q's mixed up so to speak :D

Edit: Sorry if it's a stupid question, been having a slow day, think the heat is finally getting to me.

cool, imo you wouldn't want a setters for the actual account class properties if you are relying on helpers. (note i am not a c# dev - assuming this is c#, looks like it)
 
You shouldn't have helper libraries for this. The methods should be part of the Account objects.

I would expect something more along the lines of:
SavingsAccount account = accountRepository.Get(accountId);
account.Deposit(12500);

// Then to retrieve the balance you would just say:
return account.TotalBalance;
 
You shouldn't have helper libraries for this. The methods should be part of the Account objects.

I would expect something more along the lines of:
SavingsAccount account = accountRepository.Get(accountId);
account.Deposit(12500);

// Then to retrieve the balance you would just say:
return account.TotalBalance;
Agreed. @Solarion you already have a repo interface pattern for your api. This should be similar. Have a model library as a blueprint and your repo class with your crud and logic methods. Also, consider inheritance. Both should belong to a base class of account with common properties and methods. Watch your loading though.
 
Use code tags.

Use bigint for money.

Identify what is really different between a current account and a savings account.

Chances are they are the same thing, and don’t need a hierarchy.
On a data level they are probably identical, a ledger of debits and credits, although they probably have different behavior.
I generally avoid inheritance and favour functions and composition. You don’t want a flat hierarchy when you want to add fixed deposit, money market, global, etc accounts
 
Last edited:
That's the thing is I'm thinking about what if new account types are added in future.

Anyway just going over all your suggestions.
 
Below is a quick mockup of what I would do as far as a starting point for interfaces and classes. In my mind, I savings account would typically have the same behaviour as a Current account so I would inherit that as a starting point to not rewrite, but in my example I left them completely separate.

I haven't including any samples for the repository itself.

C#:
    public enum AccountType
    {
        Savings = 1,
        Current = 2
    }

    public interface IAccount
    {
        public string AccountNumber { get; }

        public ICustomer Customer { get; }

        public decimal Balance { get; }

        public AccountType AccountType { get; }

        public void Open();
    }

    public interface ITransactionalAccount : IAccount
    {
        public decimal MinimumBalance { get; set; }

        public void Deposit(decimal amount);

        public void Withdraw(decimal amount);
    }

    public interface IInterestBearingAccount : IAccount
    {
        public decimal InterestRate { get; }
        public decimal CalculateInterest();
    }
   
    public class CurrentAccount : ITransactionalAccount
    {
        private IAccountRepository accountRepository;

        public string AccountNumber { get; protected set; }

        public ICustomer Customer { get; protected set; }

        public AccountType AccountType { get => AccountType.Current; }

        public decimal Balance { get; protected set; }

        public decimal MinimumBalance { get; set; }

        public CurrentAccount(IAccountRepository accountRepository, string accountNumber, ICustomer customer, decimal balance, decimal minimumBalance)
        {
            this.accountRepository = accountRepository;
            this.AccountNumber = accountNumber;
            this.Customer = customer;
            this.Balance = balance;
            this.MinimumBalance = minimumBalance;
        }

        public void Deposit(decimal amount)
        {
            this.Balance += amount;

            this.Update();
        }

        public void Withdraw(decimal amount)
        {
            decimal newBalance = this.Balance - amount;

            if (this.MinimumBalance < newBalance)
                throw new Exception("Insufficient funds");

            this.Balance = newBalance;

            this.Update();
        }

        private void Update()
        {
            accountRepository.Update(this);
        }

        public void Open()
        {
            this.AccountNumber = accountRepository.Create(this);
        }
    }

    public class SavingsAccount : ITransactionalAccount, IInterestBearingAccount
    {
        private IAccountRepository accountRepository;

        public string AccountNumber { get; protected set; }

        public ICustomer Customer { get; protected set; }

        public AccountType AccountType { get => AccountType.Current; }
             
        public decimal Balance { get; protected set; }

        public decimal MinimumBalance { get; set; }

        public decimal InterestRate { get; set; }

        public SavingsAccount(IAccountRepository accountRepository, string accountNumber, ICustomer customer, decimal balance, decimal minimumBalance, decimal interestRate)
        {
            this.accountRepository = accountRepository;
            this.AccountNumber = accountNumber;
            this.Customer = customer;
            this.Balance = balance;
            this.MinimumBalance = minimumBalance;
            this.InterestRate = interestRate;
        }

        public void Deposit(decimal amount)
        {
            this.Balance += amount;

            this.Update();
        }

        public void Withdraw(decimal amount)
        {
            decimal newBalance = this.Balance - amount;

            if (this.MinimumBalance < newBalance)
                throw new Exception("Insufficient funds");

            this.Balance = newBalance;

            this.Update();
        }

        private void Update()
        {
            accountRepository.Update(this);
        }

        public void Open()
        {
            this.AccountNumber = accountRepository.Create(this);
        }
               
        public decimal CalculateInterest()
        {
            // Calculate interest and return - just used as an example
        }

    }

    // AccountManager / AccountFactory for creating concrete implementations
    public class AccountManager
    {
        private IAccountRepository accountRepository;

        public AccountManager(IAccountRepository accountRepository)
        {
            this.accountRepository = accountRepository;
        }

        // The accountRepository Get method can call the factory / manager method
        public IAccount CreateAccount(AccountType accountType, string accountNumber, ICustomer customer, decimal balance, decimal? minimumBalance, decimal? interestRate)
        {
            switch(accountType)
            {
                case AccountType.Current:
                    return new CurrentAccount(accountRepository, accountNumber, customer, balance, minimumBalance.Value);
                case AccountType.Savings:
                    return new SavingsAccount(accountRepository, accountNumber, customer, balance, minimumBalance.Value, interestRate.Value);
                default:
                    throw new NotImplementedException();
            }
        }

        public IAccount CreateAccount(AccountType accountType, ICustomer customer, decimal? minimumBalance, decimal? interestRate)
        {
            switch(accountType)
            {
                case AccountType.Current:
                    return new CurrentAccount(accountRepository, null, customer, 0, minimumBalance.Value);
                case AccountType.Savings:
                    return new SavingsAccount(accountRepository, null, customer, 0, minimumBalance.Value, interestRate.Value);
                default:
                    throw new NotImplementedException();
            }
        }
    }

    public interface IAccountRepository
    {
        public string Create(IAccount account);

        public void Update(IAccount account);

        public IAccount Get(string accountNumber, ICustomer customer);
    }

    public interface ICustomer
    {
        public int CustomerId { get; }
    }

    public class Individual : ICustomer
    {
        public int CustomerId { get; internal set; }
    }

    public class Company : ICustomer
    {
        public int CustomerId { get; internal set; }
    }
 
Ok Wow. Similar to mine yet very different in the way you manage Interfaces; mine is more dependent on inheritance through abstraction which has bothered me, too many dependencies. That is really nice! The way you have split up the types by interest bearing and transaction accounts. Also as opposed to mine, you can now add various other account types.

Edit: Actually instead of just talking about it, I will quickly finish up what I have and stick it on GitHub and post a link here, most likely tomorrow so you can see the difference. I was headed in the right direction just off the beaten track, quite far off :X3:
 
Last edited:
That's the thing is I'm thinking about what if new account types are added in future.

Anyway just going over all your suggestions.
For account types, I would save them in the database and map them into enums, so that if a new type is introduced or an account type is renamed, we just update the db.
 
Identify what is really different between a current account and a savings account.

Chances are they are the same thing, and don’t need a hierarchy.
On a data level they are probably identical, a ledger of debits and credits, although they probably have different behavior.
Exactly, plus separating them not only compounds the amount of boilerplate; it makes the structure too rigid and inflexible; it's inevitably going to suck down the road when the marketing guys get creative.

I generally avoid inheritance and favour functions and composition. You don’t want a flat hierarchy when you want to add fixed deposit, money market, global, etc accounts
That's the correct approach IMO; its more flexible ito design, easier to test and far easier to adapt to future change requests e.g. the addition of a DSL to offset the cost of changes (time, dev, Q&A, ...) by empowering the marketing team.
 
@_kabal_

In light of those pointers you gave, with regards to functions and composition. I'm literally going to try and step out into a different realm here and see what I can put together with those in mind. I know what you are saying because the larger the program (more classes and interfaces) I CAN see a pattern.
 
Last edited:
@_kabal_

In light of those pointers you gave, with regards to functions and composition. I'm literally going to try and step out into a different realm here and see what I can put together with those in mind. I know what you are saying because the larger the program (more classes and interfaces) I CAN see a pattern.
Another unintended consequence of the liberal use of IOP (interface oriented programming) abstractions is that you can easily lose local reasoning aka separation logic;
gauging the ability for the next developer to easily reason about your code without having to first jump through a maze of definitions; spread across multiple files.

This can be thought of as equally undesirable a code smell as the historic spaghetti code was.
spaghetti-code.png


In OOP this is also somewhat synonymous with Lasagna code.

Btw this doesn't mean your code should have no interfaces; rather that you should always weigh up the cost of designing with interfaces against other approaches.
 
^
Sounds a bit like most of the namespaces and libraries in Visual Studio. Ever waded through some of them? Interfaces Everywhere.

Take System.Data and go explore around in there a little. You can see my confusion, so many different approaches to doing something.

In short, you basically want interface that define the logic for your classes. You want your classes to be closed for change but open to extension. Seems simple. Anyway I'm still plodding along with this demo trying not to Interface the hell out of it.
 
Last edited:
OOP has recommended that programmers should favour composition over inheritance; as a way to avoid tight coupling i.e. Law of Demeter -- the previous interface post example covers a bit of that style of approach. Tagging on generics can substantially increase that flexibility but it doesn't help to simplify either testability, local reasoning, ...

OOP's primary units of construction are classes; which were made more flexible by the addition of interfaces.
...but these are not the only constructs that can be used to deal with tight coupling -- e.g. functions are smaller construction units that also can be leveraged both in terms of a flexible interface (high order functions), generics...

Simple example:
I could define an interface for an IAccount and then build different variations e.g. Cheque, Savings, ... with the relevant computations baked in. Testing would require me to not only test the Cheque plugin and the Savings plugin independently but also more often than not tie in a dependency injection framework to facilitate mocking of the production environment ties; -- similarly shared computations could be split off into another interface, or two, or ....

A different way to approach this is to modularise all computations down to a set of very simple functions with a common function interface; ideally pure and total functions; because that means that each of the functions can be tested independently without the need for dependency injection / mocking.

Generic Account: configured features
C#:
enum Feature {
  Interest, AdminCharge
}

// Pure functions that can be tested in isolation
Func<Account, Account> CalcInterest => ....
Func<Account, Account> CalcAdminCharge => ....


// Higher Order Function
static Func<Account, Account>[] ComputationLookup(Feature[] features,  Map<Feature, Func<Account, Account>> computeConfig) {
  ...
}

class GenericAccount {
  Feature[] Features
  ...
  // compute periodic account changes using Feature Computation Lookup.
}


Comment about .Net API design e.g. System.Data
Languages unlike most codebases have to maintain for the most part a level of backward compatibility; a lot of .Net was built during a time when the original OOP idea of inheritance was starting to lose favour in light of the "Law of demeter"; hence the concept of interfaces was still consider a new approach when .Net 1.0 was released.

Not many languages have the luxury of going back and simplifying their API when new syntax feature sets are added. Hence its quite normal for standard libraries to be a veritable mishmash of different design approaches over the lifespan of the language.

As example:
Whilst Interfaces came in .Net 1.0; the notion of using functions as flexible units of construction (1st class functions); was not added until .Net 3.0 (5 years later).
 
Last edited:
Top
Sign up to the MyBroadband newsletter
X