I'm currently working on doing some fundamental practice around Objects after spending years coding in C# but in a very procedural way, I'm also starting to try and learn SOLID. My main concerns on my code are the validation rules inside the Auction class and throwing so many exceptions as it feels wrong, I feel like I should be implementing some sort of List<ValidationResults>
or even a PlaceBidResult class
but then that feels like I'm not encapsulating the logic behind placing a bid in the right class?
Auction Class - This is basically the brain of the whole system and allows for placing and removing bids, setting reserve prices etc.
public class Auction
{
public string ItemName { get; private set; }
public double ReservePrice { get; private set; } = 0;
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; set; }
List<Bid> bids;
public IEnumerable<Bid> Bids
{
get
{
return bids.AsEnumerable();
}
}
public int CurrentHighestBid
{
get
{
return bids.Max(c => (int?)c.Value) ?? 0;
}
}
private const int REPUTATION_REQUIRED_TO_BID = 10;
private const double MAXIMUM_ALLOWED_DURATION = 10;
public Auction(string itemName, DateTime startDate, DateTime endDate)
{
if (string.IsNullOrEmpty(itemName))
throw new NullReferenceException("Must provide a valid item name");
this.ItemName = itemName;
if (startDate < DateTime.Now.AddMinutes(-10)) //Gives a ten minute grace to prevent this failing if DateTime.Now is passed in.
throw new ArgumentOutOfRangeException(nameof(startDate), startDate, "Auctions can't start in the past");
this.StartDate = startDate;
if (endDate < DateTime.Now || endDate > StartDate.AddDays(MAXIMUM_ALLOWED_DURATION))
{
throw new ArgumentOutOfRangeException(nameof(endDate), endDate, $"Auctions can't end in the past and the duration of the auction must be less than {MAXIMUM_ALLOWED_DURATION} days");
}
this.EndDate = endDate;
bids = new List<Bid>();
}
public void PlaceBid(double value, User user)
{
//Check that the bid is valid. I.e are their bids that are higher than us already
ValidateBid(value, user);
bids.Add(new Bid(value, user));
Console.WriteLine("Bid Added " + value);
}
private void ValidateBid(double value, User user)
{
if (CurrentHighestBid > value)
throw new ArgumentException("There are already higher bids than this", nameof(value));
if (value < 0)
throw new ArgumentException("You cannot bid negative amounts", nameof(value));
if (value < ReservePrice)
throw new ArithmeticException("Bid value cannot be lower than the reserve price of an item");
if (StartDate > DateTime.Now)
throw new ArgumentException("Auction hasn't started yet!");
if (user.Reputation < REPUTATION_REQUIRED_TO_BID)
throw new ArgumentException("User does not have enough reputation to bid on these", nameof(user.Reputation)); //Is throwing an exception here the correct way to go?
}
public void RemoveBid(double value, User user)
{
var bid = bids.Where(c => c.Value == value).FirstOrDefault();
if (bid != null)
bids.Remove(bid);
}
public void SetReservePrice(double value)
{
if (value < 0)
throw new ArithmeticException("Reserve price cannot be below zero");
this.ReservePrice = value;
}
}
}
Bid Class - right now this is basically a data bag or "anemic domain model" could this be improved with some kind of logic of its own?
public class Bid
{
public double Value { get; private set; }
public User User { get; private set; }
public Bid(double value, User user)
{
this.Value = value;
this.User = user;
}
}