Добавлен lentinant
Есть задача. Надо сделать что-то типа устройства сканера для супермаркета, который отмечает товары. Каждый товар имеет свое обозначение и цену за единицу. Также, на товар может быть акция - определенное количество приобретается за фиксированную цену (применяется раз). Наше устройство должно уметь назначать новую цену для продуктов, считывать продукт "на чек", и выдавать конечную стоимость на этом "чеке".
Вот моя реализация
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PricingLibrary
{
//класс обычного продукта
public class Product
{
//обозначение или название
public string code { private set; get; }
//цена за единицу продукта
protected double pricePerUnit { private set; get; }
//конструктор
public Product (string code, double pricePerUnit)
{
this.code = code;
this.pricePerUnit = pricePerUnit;
}
//метод для вычисления стоимости count единиц
public virtual double CalculateCost(int count)
{
return pricePerUnit * count;
}
}
//продукт по акции
public class VolumeProduct : Product
{
//цена набора
protected double pricePerVolume { private set; get; }
//сколько единиц входит в набор
protected int volumeSize { private set; get; }
//конструктор
public VolumeProduct(string code, double pricePerUnit, double pricePerVolume, int volumeSize):base(code, pricePerUnit)
{
this.pricePerVolume = pricePerVolume;
this.volumeSize = volumeSize;
}
//метод для расчета стоимости с учетом акции
public override double CalculateCost(int count)
{
if(count < volumeSize)
return base.CalculateCost(count);
else
return (count - volumeSize) * pricePerUnit + pricePerVolume;
}
}
//информация о покупке определенного продукта
class ProductCounter
{
//сколько единиц продукта было куплено
private int count = 0;
//непосредственно сам продукт
private Product product;
//конструктор
public ProductCounter(Product product)
{
this.product = product;
}
//покупка нового продукта
public void AddProductUnit()
{
count++;
}
//расчет текущей стоимости всех единиц продукта
public double CalculatePrice()
{
return product.CalculateCost(count);
}
//сброс счетчика
public void Reset()
{
count = 0;
}
}
//класс непосредственно устройства
public class PointOfSaleTerminal
{
//словарь, хранящий инфу об обозначениях и соответствующих им продуктах
private Dictionary<string, ProductCounter> allProducts = new Dictionary<string, ProductCounter>();
//метод для установки цены за продукты
public void SetPricing(params Product[] productList)
{
foreach(Product product in productList)
{
allProducts.Add(product.code, new ProductCounter(product));
}
}
//сканирование товара
public void Scan(string productName)
{
allProducts[productName].AddProductUnit();
}
//расчет общей стоимости покупки
public double CalculateTotal()
{
double value = 0;
ProductCounter[] productList = allProducts.Select(item => item.Value).ToArray();
foreach(ProductCounter product in productList)
{
value += product.CalculatePrice();
}
return value;
}
//сброс, для возможности обработки новой покупки
public void Reset()
{
ProductCounter[] productList = allProducts.Select(item => item.Value).ToArray();
foreach (ProductCounter product in productList)
{
product.Reset();
}
}
}
}
Вопрос следующий - что надо сделать с этим кодом, чтобы он соответствовал стандартам SOLID? OCP и LSP, вроде как, соблюдены, да и то не факт. Мне нужны хотя бы наводки.
Принятый ответ
lentinant, мое понимание SOLID:
S - один класс на одну задачу, сложные задачи разбиваются до подзадач и, соответственно, получаем один класс на большую задачу и по классу на подзадачу. Условно можно выразить так "если класс занимается выпеканием хлеба, то он не должен заниматься его доставкой". Важно не увлекаться дроблением сверх меры на этапе проектирования - если видишь что класс начинает разбухать и обростать группами не связанных методов, то самое время использовать этот принцип.
O - избегать изменения контрактов уже стабилизировавшегося кода. Применяется когда код уже может где-то использоваться как зависимость. По сути это требование обратной совместимости - расширять функционал можно и нужно, но старый код, зависящий от твоего, должен работать даже после превращения калькулятора в подводную лодку с вертикальным взлетом.
L - требование, согласно которому экземпляры родительских объектов должно быть можно заменить экземплярами дочерних объектов, не нарушая целостности программы. По сути это критерий, по которому можно определить и устранить избыточное или неправильное наследование, заменив его наследованием обоих объектов от общего родителя или агрегацией или хоть чертом лысым - сам принцип ничего не говорит о том, как именно его надо выпонять. Есть небольшой нюанс - применяется этот принцип только к экземплярам классов, а не к ссылкам или самому дереву наследования - если, например, где-то есть ссылка на A, который родительский класс для B, но по факту там используются только экземпляры классов B, C и D, то "nothing to do here".
I - аналог S для интерфейсов. Его главная идея в том, что не стоит перегружать класс, реализующий интерфейс, лишними методами.
D - суть в том, чтобы разорвать связи между объектами, находящимися на разных уровнях абстракции. Применяется в обоих направлениях - и для менее абстрактных объектов, включенных в более абстрактный и наоборот, более того - применяется не только при прямом включении, но и к любым другим ссылкам. Предположим нам нужно составить програмную модель кирпичной стены, класс стены не должен ничего знать о конкретных реализациях кирпичей и работать с любыми кирпичами, какие ему дадут, а кирпичи не должны напрямую зависеть от конкретных реализаций стены и быть пригодны к использованию в любой стене (или другой конструкции, если модель подразумевает не только стены).
S - один класс на одну задачу, сложные задачи разбиваются до подзадач и, соответственно, получаем один класс на большую задачу и по классу на подзадачу. Условно можно выразить так "если класс занимается выпеканием хлеба, то он не должен заниматься его доставкой". Важно не увлекаться дроблением сверх меры на этапе проектирования - если видишь что класс начинает разбухать и обростать группами не связанных методов, то самое время использовать этот принцип.
O - избегать изменения контрактов уже стабилизировавшегося кода. Применяется когда код уже может где-то использоваться как зависимость. По сути это требование обратной совместимости - расширять функционал можно и нужно, но старый код, зависящий от твоего, должен работать даже после превращения калькулятора в подводную лодку с вертикальным взлетом.
L - требование, согласно которому экземпляры родительских объектов должно быть можно заменить экземплярами дочерних объектов, не нарушая целостности программы. По сути это критерий, по которому можно определить и устранить избыточное или неправильное наследование, заменив его наследованием обоих объектов от общего родителя или агрегацией или хоть чертом лысым - сам принцип ничего не говорит о том, как именно его надо выпонять. Есть небольшой нюанс - применяется этот принцип только к экземплярам классов, а не к ссылкам или самому дереву наследования - если, например, где-то есть ссылка на A, который родительский класс для B, но по факту там используются только экземпляры классов B, C и D, то "nothing to do here".
I - аналог S для интерфейсов. Его главная идея в том, что не стоит перегружать класс, реализующий интерфейс, лишними методами.
D - суть в том, чтобы разорвать связи между объектами, находящимися на разных уровнях абстракции. Применяется в обоих направлениях - и для менее абстрактных объектов, включенных в более абстрактный и наоборот, более того - применяется не только при прямом включении, но и к любым другим ссылкам. Предположим нам нужно составить програмную модель кирпичной стены, класс стены не должен ничего знать о конкретных реализациях кирпичей и работать с любыми кирпичами, какие ему дадут, а кирпичи не должны напрямую зависеть от конкретных реализаций стены и быть пригодны к использованию в любой стене (или другой конструкции, если модель подразумевает не только стены).
Еще хочу сказать, что слепое следование всем принципам ни к чему хорошему не приводит - важно понимать грань, за которой начинается ад и содомия и вовремя остановиться.
`
ОЖИДАНИЕ РЕКЛАМЫ...
Чтобы оставить комментарий, пожалуйста, войдите на сайт.
Отредактирован lentinant
перегружунемного дополню вопрос своими соображениями (как я понял аспекты SOLID)Насколько я понял, L - просто концепция, при которой в программе экземпляр класса можно заменить экземпляром его дочернего класса, без ущерба программе; O - по сути, не должно быть классов, выполняющих "избыточную" работу, то есть, если мне надо выполнять некоторые базовые действия, и в некоторых случаях, кроме базовых выполнять еще дополнительные, то надо создать базовый класс с базовыми действиями, а дополнительные прописывать уже в классе, наследующем базовый; I - если методы класса можно разделить на тематические группы, то лучше всего каждую группу делать отдельным интерфейсом.
Отредактирован prog
S - один класс на одну задачу, сложные задачи разбиваются до подзадач и, соответственно, получаем один класс на большую задачу и по классу на подзадачу. Условно можно выразить так "если класс занимается выпеканием хлеба, то он не должен заниматься его доставкой". Важно не увлекаться дроблением сверх меры на этапе проектирования - если видишь что класс начинает разбухать и обростать группами не связанных методов, то самое время использовать этот принцип.
O - избегать изменения контрактов уже стабилизировавшегося кода. Применяется когда код уже может где-то использоваться как зависимость. По сути это требование обратной совместимости - расширять функционал можно и нужно, но старый код, зависящий от твоего, должен работать даже после превращения калькулятора в подводную лодку с вертикальным взлетом.
L - требование, согласно которому экземпляры родительских объектов должно быть можно заменить экземплярами дочерних объектов, не нарушая целостности программы. По сути это критерий, по которому можно определить и устранить избыточное или неправильное наследование, заменив его наследованием обоих объектов от общего родителя или агрегацией или хоть чертом лысым - сам принцип ничего не говорит о том, как именно его надо выпонять. Есть небольшой нюанс - применяется этот принцип только к экземплярам классов, а не к ссылкам или самому дереву наследования - если, например, где-то есть ссылка на A, который родительский класс для B, но по факту там используются только экземпляры классов B, C и D, то "nothing to do here".
I - аналог S для интерфейсов. Его главная идея в том, что не стоит перегружать класс, реализующий интерфейс, лишними методами.
D - суть в том, чтобы разорвать связи между объектами, находящимися на разных уровнях абстракции. Применяется в обоих направлениях - и для менее абстрактных объектов, включенных в более абстрактный и наоборот, более того - применяется не только при прямом включении, но и к любым другим ссылкам. Предположим нам нужно составить програмную модель кирпичной стены, класс стены не должен ничего знать о конкретных реализациях кирпичей и работать с любыми кирпичами, какие ему дадут, а кирпичи не должны напрямую зависеть от конкретных реализаций стены и быть пригодны к использованию в любой стене (или другой конструкции, если модель подразумевает не только стены).
Отредактирован lentinant
prog: