patterncsharpMinor
5 Customers, but only 1 Bank Teller
Viewed 0 times
butbanktelleronlycustomers
Problem
Recently, as part of an interview, I was given a (seemingly) simple code prompt: To simulate a Bank with 5 customers and one teller, allowing them to deposit to and withdraw from their accounts, but with each customer on its own thread while only allowing one to access the teller at any given time.
I've a CompSci degree, VERY limited experience with multi-threading and fairly new to C#. I would just like someone to critique my code a bit. I got some general feedback but would like some more specific insight.
I was told I:
```
namespace BankSimulationThreaded
{
class Program
{
// Logs the number of completed transactions.
static int transactions = 0;
// Creates a new teller that will be shared by all customers.
static Teller tel = new Teller();
// Creates a random number generator, ran, that will be used to create random integers for the transactions.
static Random ran = new Random();
static void Main(string[] args)
{
// Initializes a new thread for each customer.
Thread bankCustomer1 = new Thread(new ThreadStart(new BankCustomer("Joe").runCustomer));
Thread bankCustomer2 = new Thread(new ThreadStart(new BankCustomer("Bob").runCustomer));
Thread bankCustomer3 = new Thread(new ThreadStart(new BankCustomer("Steve").runCustomer));
Thread bankCustomer4 = new Thread(new ThreadStart(new BankCustomer("Frank").runCustomer));
Thread bankCustomer5 = new Thread(new ThreadStart(new BankCustomer("Jess").runCustomer));
// Starts the treads.
bankCustomer1.Start();
bankCustomer2.Start();
bankCustomer3.Start();
bankCustomer4.Start();
bankCustomer5.Start();
// Prevent
I've a CompSci degree, VERY limited experience with multi-threading and fairly new to C#. I would just like someone to critique my code a bit. I got some general feedback but would like some more specific insight.
I was told I:
- showed incomplete understanding of spawning threads and when to lock a thread
- the program contains dead code (which I can't find, other than a single accessor that did not get used)
```
namespace BankSimulationThreaded
{
class Program
{
// Logs the number of completed transactions.
static int transactions = 0;
// Creates a new teller that will be shared by all customers.
static Teller tel = new Teller();
// Creates a random number generator, ran, that will be used to create random integers for the transactions.
static Random ran = new Random();
static void Main(string[] args)
{
// Initializes a new thread for each customer.
Thread bankCustomer1 = new Thread(new ThreadStart(new BankCustomer("Joe").runCustomer));
Thread bankCustomer2 = new Thread(new ThreadStart(new BankCustomer("Bob").runCustomer));
Thread bankCustomer3 = new Thread(new ThreadStart(new BankCustomer("Steve").runCustomer));
Thread bankCustomer4 = new Thread(new ThreadStart(new BankCustomer("Frank").runCustomer));
Thread bankCustomer5 = new Thread(new ThreadStart(new BankCustomer("Jess").runCustomer));
// Starts the treads.
bankCustomer1.Start();
bankCustomer2.Start();
bankCustomer3.Start();
bankCustomer4.Start();
bankCustomer5.Start();
// Prevent
Solution
A list of things you could improve:
It’s very easy to see in Visual Studio where a class starts and ends.
See: https://msdn.microsoft.com/en-us/library/vstudio/b2s063f7%28v=vs.100%29.aspx
Not
public String Name { get; set; }
And use the appropriate access modifier
return deposit ? "Deposit" : "Withdrawal";
#region Properties
if ( amount > ValueBalance )
return false;
//Create some customers
var customers = new List
{
new BankCustomer { Name = "Joe" },
new BankCustomer { Name = "Bob" },
new BankCustomer { Name = "Steve" },
new BankCustomer { Name = "Frank" },
new BankCustomer { Name = "Jess" }
};
//Run the customers
customers.ForEach( x => Task.Run( () => x.RunCustomer() ) );
public static class ObjectStorage
{
// Logs the number of completed transactions.
static ObjectStorage()
{
Teller = new Teller();
Rnd = new Random();
}
public static Int32 Transactions { get; set; }
// Creates a new teller that will be shared by all customers.
public static Teller Teller { get; private set; }
// Creates a random number generator, _rnd, that will be used to create random integers for the transactions.
public static Random Rnd { get; private set; }
}
public class Program
{
static void Main( String[] args )
{
//Create some customers
var customers = new List
{
new BankCustomer { Name = "Joe" },
new BankCustomer { Name = "Bob" },
new BankCustomer { Name = "Steve" },
new BankCustomer { Name = "Frank" },
new BankCustomer { Name = "Jess" }
};
//Run the customers
customers.ForEach( x => Task.Run( () => x.RunCustomer() ) );
// Prevents program from closing so that user may read output.
while ( ObjectStorage.Transactions
/// Gets the balance of the customer.
///
/// The balance of the customer.
public Int32 Balance { get; private set; }
///
/// Gets or sets the name of the customer.
///
/// The name of the customer.
public String Name { get; set; }
#endregion
#region Ctor
///
/// Creates a new instance of the class.
///
public BankCustomer()
{
Balance = 1000;
}
#endregion
#region Public Members
public void RunCustomer()
{
// When Deposit is true, make Deposit. Else, make withdrawal.
var deposit = true;
// amt is used to determine the amount of the transaction. It is a random int between 1 and 5000.
// Transact until total transactions exceed hit the specified value.
while ( ObjectStorage.Transactions
/// Subtracts the given amount from the current balance.
///
/// The amount to subtract.
/// Returns false if amount exceeds balance.
private Boolean Withdraw( Int32 amount )
{
// If amount does not exceed balance or amount in vault, subtract amount from balance.
if ( amount > Balance )
{
Console.Write( "{0}'s account has ", Name );
return false;
}
if ( !ObjectStorage.Teller.Withdraw( amount ) )
{
- Do not write comments like this:
// End of class Teller
It’s very easy to see in Visual Studio where a class starts and ends.
- Add XML comments to your classes, method, properties, fields etc.
See: https://msdn.microsoft.com/en-us/library/vstudio/b2s063f7%28v=vs.100%29.aspx
- In C# Classes, Methods, Properties and events are written in Pascal case:
GetBal not getBal- Do not use ‘uncommon’ abbreviations if the full word is not very long:
GetBalance not GetBal- Always specify access modifiers https://msdn.microsoft.com/en-us/library/ms173121.aspx
Not
class BankCustomer write public class BankCustomer- Use auto Properties instead of global variables:
public String Name { get; set; }
And use the appropriate access modifier
public Int32 Balance { get; private set; }- Make as much methods as possible private or protected (Keep interfaces slim)
- Use inline
ifstatements (aka ternary operator):
return deposit ? "Deposit" : "Withdrawal";
- Use
String.Formatfor creating formatted strings or writing to the console:Console.Write( "{0}'s account has ", Name );https://msdn.microsoft.com/en-us/library/system.string.format%28v=vs.110%29.aspx
- Don’t use redundant qualifier
this, base
this.Deposit( amt ); Deposit( amt ); would be better- The
ifat the end of the main method doesn’t work. Your program will close immediately (if the threading stuff is done right). You should use a while loop:
while ( _transactions
- Use Regions to structure your code:
#region Properties
- Invert if statements to reduce nesting:
if ( amount > ValueBalance )
return false;
- Do not create classes inside of other classes. ( only do this if you need a private class which is only used in the scope of the other class )
- Do not use
Thread.Sleep inside of a lock statement
- Use the var keyword if possible
- Use
Environment.NewLine instead of "\r\n"
- Create the customers as a collection so you can start the threads inside of a loop:
//Create some customers
var customers = new List
{
new BankCustomer { Name = "Joe" },
new BankCustomer { Name = "Bob" },
new BankCustomer { Name = "Steve" },
new BankCustomer { Name = "Frank" },
new BankCustomer { Name = "Jess" }
};
//Run the customers
customers.ForEach( x => Task.Run( () => x.RunCustomer() ) );
Here is an improved version of your code.
(This is not a perfect solution… there is still a lot to improve)
``public static class ObjectStorage
{
// Logs the number of completed transactions.
static ObjectStorage()
{
Teller = new Teller();
Rnd = new Random();
}
public static Int32 Transactions { get; set; }
// Creates a new teller that will be shared by all customers.
public static Teller Teller { get; private set; }
// Creates a random number generator, _rnd, that will be used to create random integers for the transactions.
public static Random Rnd { get; private set; }
}
public class Program
{
static void Main( String[] args )
{
//Create some customers
var customers = new List
{
new BankCustomer { Name = "Joe" },
new BankCustomer { Name = "Bob" },
new BankCustomer { Name = "Steve" },
new BankCustomer { Name = "Frank" },
new BankCustomer { Name = "Jess" }
};
//Run the customers
customers.ForEach( x => Task.Run( () => x.RunCustomer() ) );
// Prevents program from closing so that user may read output.
while ( ObjectStorage.Transactions
/// Gets the balance of the customer.
///
/// The balance of the customer.
public Int32 Balance { get; private set; }
///
/// Gets or sets the name of the customer.
///
/// The name of the customer.
public String Name { get; set; }
#endregion
#region Ctor
///
/// Creates a new instance of the class.
///
public BankCustomer()
{
Balance = 1000;
}
#endregion
#region Public Members
public void RunCustomer()
{
// When Deposit is true, make Deposit. Else, make withdrawal.
var deposit = true;
// amt is used to determine the amount of the transaction. It is a random int between 1 and 5000.
// Transact until total transactions exceed hit the specified value.
while ( ObjectStorage.Transactions
/// Subtracts the given amount from the current balance.
///
/// The amount to subtract.
/// Returns false if amount exceeds balance.
private Boolean Withdraw( Int32 amount )
{
// If amount does not exceed balance or amount in vault, subtract amount from balance.
if ( amount > Balance )
{
Console.Write( "{0}'s account has ", Name );
return false;
}
if ( !ObjectStorage.Teller.Withdraw( amount ) )
{
Code Snippets
public static class ObjectStorage
{
// Logs the number of completed transactions.
static ObjectStorage()
{
Teller = new Teller();
Rnd = new Random();
}
public static Int32 Transactions { get; set; }
// Creates a new teller that will be shared by all customers.
public static Teller Teller { get; private set; }
// Creates a random number generator, _rnd, that will be used to create random integers for the transactions.
public static Random Rnd { get; private set; }
}
public class Program
{
static void Main( String[] args )
{
//Create some customers
var customers = new List<BankCustomer>
{
new BankCustomer { Name = "Joe" },
new BankCustomer { Name = "Bob" },
new BankCustomer { Name = "Steve" },
new BankCustomer { Name = "Frank" },
new BankCustomer { Name = "Jess" }
};
//Run the customers
customers.ForEach( x => Task.Run( () => x.RunCustomer() ) );
// Prevents program from closing so that user may read output.
while ( ObjectStorage.Transactions < 100 )
Thread.Sleep( 100 );
Console.WriteLine( "The Bank is now closed. Press any key to exit." );
Console.ReadKey();
}
}
public class BankCustomer
{
#region Properties
/// <summary>
/// Gets the balance of the customer.
/// </summary>
/// <value>The balance of the customer.</value>
public Int32 Balance { get; private set; }
/// <summary>
/// Gets or sets the name of the customer.
/// </summary>
/// <value>The name of the customer.</value>
public String Name { get; set; }
#endregion
#region Ctor
/// <summary>
/// Creates a new instance of the <see cref="BankCustomer" /> class.
/// </summary>
public BankCustomer()
{
Balance = 1000;
}
#endregion
#region Public Members
public void RunCustomer()
{
// When Deposit is true, make Deposit. Else, make withdrawal.
var deposit = true;
// amt is used to determine the amount of the transaction. It is a random int between 1 and 5000.
// Transact until total transactions exceed hit the specified value.
while ( ObjectStorage.Transactions < 100 )
{
Console.WriteLine("{0} approaches the window. {0} is making a {1}.", Name, DepositOrWith(deposit));
// Assign amt a random number, using Random _rnd, between 1 and 5000.
var amt = ObjectStorage.Rnd.Next(1, 5000);
// Locks the resource so that it may only be used by one thread at a time.
lock ( ObjectStorage.Teller )
{
if ( deposit )
{
// Deposit amount deposited into this threads account, the vault, and set next transaction to be a withdrawal.
Deposit( amt );
ObjectStorage.Telle#region Usings
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
#endregion
namespace Bank
{
public class Program
{
static void Main( String[] args )
{
//Create some customers
var customers = new List<Customer>
{
new Customer { Name = "Joe" },
new Customer { Name = "Bob" },
new Customer { Name = "Steve" },
new Customer { Name = "Frank" },
new Customer { Name = "Jess" }
};
//Run the customers
customers.ForEach( x => Task.Run( () => x.MakePayments() ) );
// Prevents program from closing so that user may read output.
while ( Teller.Instance.IsWorking )
Thread.Sleep( 100 );
Console.WriteLine( "The Bank is now closed. Press any key to exit." );
Console.ReadKey();
}
}
public static class RandomHelper
{
private static readonly Random _random = new Random();
public static Int32 Next( Int32 start, Int32 end )
{
return _random.Next( start, end );
}
}
/// <summary>
/// Class representing a teller.
/// </summary>
public sealed class Teller
{
#region Fields
/// <summary>
/// Lazy used to create a teller.
/// </summary>
private static readonly Lazy<Teller> Lazy = new Lazy<Teller>( () => new Teller() );
/// <summary>
/// Object used to synchronize threads.
/// </summary>
private static readonly Object SyncRoot = new Object();
/// <summary>
/// Field used to count the number of transactions.
/// </summary>
private Int32 _transactionCounter;
#endregion
#region Properties
/// <summary>
/// Gets the teller.
/// </summary>
/// <value>The teller.</value>
public static Teller Instance
{
get { return Lazy.Value; }
}
/// <summary>
/// Gets the amount of money that is left in the vault.
/// </summary>
/// <value>The amount of money that is left in the vault.</value>
public Int32 ValueBalance { get; private set; }
/// <summary>
/// Gets a value indicating whether the teller is working or not.
/// </summary>
/// <value>A value indicating whether the teller is working or not. </value>
public Boolean IsWorking
{
get { return _transactionCounter < 100; }
}
#endregion
#region Ctor
/// <summary>
/// Creates a new instance of the <see cref="Teller" /> class.
/// </summary>
private Teller()
{
ValueBalance = 20000Context
StackExchange Code Review Q#85689, answer score: 8
Revisions (0)
No revisions yet.