ООП. Описание процесса переливания чего-то из одного сосуда в другой
В рамках изучения попробовал реализовать программу в которой можно перелить что-то (скорее всего жидкость) из одного сосуда в другой, получилась следующая архитектура.
В мейне понял что есть метод с неоднозначным вызовом.
((IStatable<IVesselState>)vesselA).getState().Volum понимаю что в нормальной ситуации это не решение.
Подскажите, пожалуйста, как сделать нормально, но не просто переименовывая методы.
Может я с построением архитектуры ошибся и вообще неправильно мыслю.
Код:
interface IState
{
}
interface IStatable<T> where T: IState
{
T getState();
}
interface ITransfuser<T> where T: IState
{
void Transfuse(ITransfuseable<T> a, ITransfuseable<T> b);
}
interface ITransfuseable<T> where T: IState
{
T getState();
}
interface IVesselState : IState
{
double Volum { get; set; }
double getMaxVolume();
}
interface IVessel : IStatable<IVesselState>, ITransfuseable<IVesselState>
{
void pour(double v);
}
public void Transfuse(ITransfuseable<IVesselState> a, ITransfuseable<IVesselState> b)
{
double canAccept = a.getState().getMaxVolume() - a.getState().Volum;
if(canAccept > 0)
{
double leftB = b.getState().Volum - canAccept;
if (leftB < 0)
{
canAccept -= Math.Abs(leftB);
}
a.getState().Volum += canAccept;
b.getState().Volum -= canAccept;
}
}
}
class VesselState : IVesselState
{
private double _maxVolume;
public double Volum { get; set; }
public VesselState(double max)
{
_maxVolume = max;
}
public double getMaxVolume()
{
return _maxVolume;
}
}
class Vessel : IVessel
{
IVesselState _state;
public Vessel(double max)
{
_state = new VesselState(max);
}
public void pour(double v)
{
_state.Volum += v;
}
public IVesselState getState()
{
return _state;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
IVessel vesselA = new Vessel(20);
vesselA.pour(10);
IVessel vesselB = new Vessel(30);
vesselB.pour(5);
ITransfuser<IVesselState> transfuser = new VesselTransfuser();
Console.WriteLine(((IStatable<IVesselState>)vesselA).getState().Volum);
Console.WriteLine(((IStatable<IVesselState>)vesselA).getState().Volum);
//Код ниже не работает: неоднозначный вызов getState()
//Console.WriteLine(vesselA.getState().Volum);
//Console.WriteLine(vesselB.getState().Volum);
transfuser.Transfuse(vesselA, vesselB);
Console.WriteLine(((IStatable<IVesselState>)vesselA).getState().Volum);
Console.WriteLine(((IStatable<IVesselState>)vesselB).getState().Volum);
}
}
Ответы (1 шт):
При проектировании объектной модели приложения надо учитывать несколько вещей:
- SRP (из SOLID) - интерфейс должен предоствалять какую-то одну роль для класса.
- Дубликаты - методы или свойства, которые дублируют друг друга. Например ваша ошибка в том, что у вас есть метод "налить", и в то же время можно налить через изменение
Volume. - Слабые связи. Абстракции друг о друге должны знать как можно меньше, но ровно столько, чтобы обеспечить друг другу нормальное поведение.
- Конкретная реализация класса может быть конкретнее, чем интерфейс, даже в публичной части. Этот фокус мне и помог закрыть
Volumeна запись извне.
Именно исправление ITransfuseable как раз и решило проблему проектирования. Остальное всё блестяшки. Переливаемое означает, что оттуда можно слить и можно налить - 2 очевидных метода.
То есть слив и залив теперь возможен только через специально предназначенные для этого методы. Например вы решите, что в бочке плохое горлышко и 10% заливаемой в нее воды будет проливаться мимо. Метод залива поможет вам это сделать не задевая реализацию переливальщика.
Интерфейсы
interface IState
{
}
interface IStatable<T> where T : IState
{
T State { get; }
}
interface ITransfuseable<T> : IStatable<T> where T : IState
{
void Pour(double value);
void Drain(double value);
}
interface ITransfuser<T, TState>
where T : ITransfuseable<TState>
where TState : IState
{
void Transfuse(T dst, T src);
}
interface IVesselState : IState
{
double Volume { get; }
double MaxVolume { get; }
}
interface IVessel : ITransfuseable<IVesselState>
{
}
interface IVesselTransfuser : ITransfuser<IVessel, IVesselState>
{
}
Реализации
class VesselState : IVesselState
{
public double Volume { get; set; }
public double MaxVolume { get; }
public VesselState(double max)
{
MaxVolume = max;
}
}
class Vessel : IVessel
{
private VesselState _state;
public IVesselState State => _state;
public Vessel(double max)
{
_state = new VesselState(max);
}
public void Pour(double value)
{
_state.Volume += value;
}
public void Drain(double value)
{
_state.Volume -= value;
}
}
class VesselTransfuser : IVesselTransfuser
{
public void Transfuse(IVessel dst, IVessel src)
{
double amount = dst.State.MaxVolume - dst.State.Volume;
if (amount > 0)
{
double leftB = src.State.Volume - amount;
if (leftB < 0)
{
amount -= Math.Abs(leftB);
}
dst.Pour(amount);
src.Drain(amount);
}
}
}
Кстати, старайтесь давать имена хотя-бы аргументам понятные, а то у вас получилось Transfuse(a, b) - это откуда куда? "Из А в Б" - подумал я, а вот и нет!
Тестирование
class Program
{
static void Main(string[] args)
{
IVessel vesselA = new Vessel(20);
vesselA.Pour(10);
IVessel vesselB = new Vessel(30);
vesselB.Pour(5);
IVesselTransfuser transfuser = new VesselTransfuser();
Console.WriteLine(vesselA.State.Volume);
Console.WriteLine(vesselB.State.Volume);
transfuser.Transfuse(vesselA, vesselB);
Console.WriteLine(vesselA.State.Volume);
Console.WriteLine(vesselB.State.Volume);
}
}
Вывод в консоль
10
5
15
0
P.S. Имена методов в C# пишутся в CamelCase, то есть с большой буквы.