Есть пара подходов, которые я могу предложить для решения подобных проблем.
- Отслеживание экземпляров фильма и их статуса и вычисление итогов по запросу.
Это будет что-то вроде класса Movie, но тогда у каждого фильма будет коллекция MovieDisc, например:
public class MovieDisc
{
public int MovieDiscId{ get; set; }
public bool InStock { get; set; }
public string Barcode { get; set; }
}
В основном, когда фильмы извлекаются или сканируются, их штрих-код идентифицирует «диск» или экземпляр и устанавливает InStock. Со стороны фильма:
public class Movie
{
// ...
public virtual ICollection<MovieDisc> Discs {get; internal set;} = new List<MovieDisc>();
public byte NumberInStock
{
get { return Discs.Count(x => x.InStock); }
}
public byte NumberAvailable
{
get { return Discs.Count(x => !x.InStock); }
}
}
Предостережение этого подхода заключается в том, что для использования этих свойств коллекция Discs должна быть загружена с нетерпением или отключит отложенную загрузку.
- Принять подход DDD к сущностям.
Domain Driven Design по существу добавляет элементы управления изменением состояния в домене. Вместо того, чтобы использовать отдельные установщики для значений, вы используете методы или действия с сущностью домена для проверки и контроля допустимых и полных изменений, разрешенных для домена.
public class Movie
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; private set; }
public Genre Genre { get; private set; }
[Display(Name = "Genre")]
[Required]
public byte GenreId { get; private set; }
[Display(Name = "Release Date")]
public DateTime ReleaseDate { get; private set; }
public DateTime DateAdded { get; private set; }
[Display(Name = "Number in Stock")]
[Range(1, 20)]
public byte NumberInStock { get; private set; }
public byte NumberAvailable { get; private set; }
public void RentOneOut()
{
if (NumberInStock <= 0)
throw new InvalidOperation("Cannot rent out a movie that has no stock.");
if (NumberAvailable <= 0)
throw new InvalidOperation("All movie copies are out.");
NumberAvailable -= 1;
}
public void ReturnOneIn()
{
if (NumberInStock <= 0)
throw new InvalidOperation("Cannot return a movie that has no stock.");
if (NumberAvailable >= NumberInStock)
throw new InvalidOperation("All movie copies are already in. Stocktake needed.");
NumberAvailable += 1;
}
}
Обратите внимание, что все сеттеры являются частными. (или внутренний, если вы хотите включить модульное тестирование). Цель состоит в том, чтобы выразить допустимые операции в отношении объекта домена в качестве метода. Это гарантирует, что несколько проверок и обновлений выполняются как единое целое, так что несколько свойств могут быть обновлены вместе, не рискуя оставить объект в неполном состоянии.
Это может быть более практичным для вашего сценария аренды:
public function ReturnRental(Rental rental)
{
if (rental == null)
throw new ArgumentNullException("rental");
rental.Return();
}
// In Rental:
public class Rental
{
// ... private setters, like in Movie.
public void Return()
{
Movie.ReturnOneIn();
DateReturned = DateTime.Today;
}
}
Вы бы хотели обработать сценарий, в котором возврат по какой-либо причине не удался. (состояние данных не синхронизировано)
Надеюсь, это даст вам несколько идей.