Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.73/15: Рейтинг темы: голосов - 15, средняя оценка - 4.73
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418

Архитектура приложения, даункаст

30.09.2021, 07:38. Показов 3097. Ответов 22
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Предположим, я хочу создать класс "ящик с инструментами". В ящике могут лежать различные инструменты: ключи, молотки, пилы и т. д. Каждый инструмент наследуется от класса Tool. Ящик с инструментами хранит объекты типа Tool, чтобы в нём могли лежать объекты разных типов. Теперь я реализую объект "сортировщик инструментов", который берёт один за одним объект Tool из ящика и раскладывает их по ящикам "ящик с молотками", "ящик с ключами", "ящик с плоскогубцами"... Сортировщик не знает какой именно инструмент лежит в типе Tool. Допустимо ли здесь проверять каждый тип (операторы as, is) или есть какая-то более удачная архитектура/паттерн?

Добавлено через 56 минут
Нашёл, что можно использовать паттерн visitor. Хорошее ли это решение?
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
30.09.2021, 07:38
Ответы с готовыми решениями:

Архитектура приложения
Существует литература по построению архитектуры приложений?

Архитектура приложения
Всем привет! Пытаюсь разобраться в следующих вопросах. есть приложение C# ( но это не важно ) которое взаимодействует с база данных...

Архитектура приложения с БД на интранете
Хочу создать следующую систему, но не могу понять, какие, где технологии мне использовать. Буду благодарен за любую помощь. Есть база...

22
1595 / 600 / 185
Регистрация: 05.12.2015
Сообщений: 970
30.09.2021, 14:18
Модель объектов должна быть абстрактной и не привязана к типам .NET
Просто создаешь enum перечисление объектов например ToolType{none, i1,i2,i2}
В базовом классе Tool виртуальное свойство например ToolType myType=>ToolType.none
В каждом наследнике override myType=>ToolType.itemX
Для ящиков создаешь базовый класс Box наследник Tool
Для каждого типа ящика override myType
При добавлении в ящик любой Tool просто сравниваешь совпадает его myType с типом ящика.
myType также необходим для базы данных.
1
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
30.09.2021, 14:28  [ТС]
У меня появилось ещё больше вопросов, извините...
1) чем ваш пример отличается от as is с последующим даункастом? Мы же теперь идентификатор типа просто храним в другом месте... нет?
2)всё-таки уместен ли паттерн посетитель (visitor)?
3)
Для ящиков создаёшь базовый класс Box наследник Tool
- не пойму, Tool наследуется от Box или наоборот Box наследуется от Tool? И самое главное, я не понимаю зачем это делать, ни ящик не является инструментом, ни инструмент не является ящиком...
Я прошу прощения, что это сообщение выглядит так душно, просто я хочу разобраться.
Большое спасибо за ответ
0
1595 / 600 / 185
Регистрация: 05.12.2015
Сообщений: 970
30.09.2021, 16:32
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    public enum ToolType { none, item1, item2 }
    abstract class Tool
    {
        public virtual ToolType myType => ToolType.none;
    }
    class ToolItem1 : Tool
    {
        public override ToolType myType => ToolType.item1;
    }
    class ToolItem2 : Tool
    {
        public override ToolType myType => ToolType.item2;
    }
    //---------------------------
    abstract class Box : Tool
    {
        public List<Tool> toolList = new List<Tool>();
        public bool CanFit( Tool tool ) => tool.myType == this.myType;
        public bool Add( Tool tool )
        {
            if( CanFit( tool ) )
            {
                toolList.Add( tool );
                return true;
            }
            else
                return false;
        }
 
    }
    class BoxItem1 : Box
    {
        public override ToolType myType => ToolType.item1;
    }
    class BoxItem2 : Box
    {
        public override ToolType myType => ToolType.item2;
    }
как использовать Box
C#
1
2
3
            BoxItem1 box1 = new BoxItem1();
            box1.Add( new ToolItem1() );
            box1.Add( new ToolItem2() );
в box1 добавиться 1, потому что второй не соответствует типу
Пройдите пошагово.
Если Add вернул true значит добавил. Логика дальше ваша.
0
 Аватар для aenye
304 / 186 / 45
Регистрация: 05.07.2018
Сообщений: 580
30.09.2021, 20:45
Цитата Сообщение от proa33 Посмотреть сообщение
Box : Tool
Что это за дичь? Тогда уже так Box: ICollection<Tool>
Цитата Сообщение от proa33 Посмотреть сообщение
public enum ToolType { none, item1, item2 }
А это зачем? Уже ведь и так есть отдельные типы. В контексте текущих требований - это бессмысленно.

Aycon,
Цитата Сообщение от Aycon Посмотреть сообщение
Допустимо ли здесь проверять каждый тип (операторы as, is) или есть какая-то более удачная архитектура/паттерн
Можете оперировать ящиком как коллекцией инструментов. Есть метод расширение OfType<> в LINQ, можете посмотреть в его сторону, скорее всего пригодится. Visitor ничем Вам не поможет в данном случае.
1
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
30.09.2021, 20:58  [ТС]
Я с помощью посетителя обошёл даункаст. Просто хочу узнать, хорошая ли это практика.
0
 Аватар для aenye
304 / 186 / 45
Регистрация: 05.07.2018
Сообщений: 580
30.09.2021, 21:03
Цитата Сообщение от Aycon Посмотреть сообщение
Я с помощью посетителя обошёл даункаст
Пример можно?)
0
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
30.09.2021, 21:35  [ТС]
Что-то типа:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class Program
    {
        static void Main(string[] args)
        {
            List<ITool> tools = new List<ITool>()
            {
                new Hammer(),
                new Hammer(),
                new Pliers(),
                new Hammer(),
                new Pliers()
            };
 
            List<Hammer> hammers = new List<Hammer>();
            List<Pliers> pliers = new List<Pliers>();
            ToolTypeInspector inspector = new ToolTypeInspector();
 
            foreach (var tool in tools)
            {
                tool.AcceptVisitor(inspector);
 
                if (inspector.LastToolType == typeof(Hammer))
                {
                    hammers.Add(inspector.LastHammer);
                }
 
                if (inspector.LastToolType == typeof(Pliers))
                {
                    pliers.Add(inspector.LastPliers);
                }
            }
 
            Console.WriteLine(hammers.Count);
            Console.WriteLine(pliers.Count);
            Console.ReadKey();
        }
    }
 
 
    interface IToolsVisitor
    {
        void VisitHammer(Hammer hammer);
        void VisitPliers(Pliers pliers);
    }
 
    class ToolTypeInspector : IToolsVisitor
    {
        public Type LastToolType { get; private set; }
 
        public Hammer LastHammer { get; private set; }
        public Pliers LastPliers { get; private set; }
 
        public void VisitHammer(Hammer hammer)
        {
            ClearRestoredTools();
            LastToolType = typeof(Hammer);
            LastHammer = hammer;
        }
 
        public void VisitPliers(Pliers pliers)
        {
            ClearRestoredTools();
            LastToolType = typeof(Pliers);
            LastPliers = pliers;
        }
 
        void ClearRestoredTools()
        {
            LastHammer = null;
            LastPliers = null;
        }
    }
 
 
    interface ITool
    {
        void AcceptVisitor(IToolsVisitor visitor);
    }
 
 
    class Hammer : ITool
    {
        public void AcceptVisitor(IToolsVisitor visitor)
        {
            visitor.VisitHammer(this);
        }
    }
 
 
    class Pliers : ITool
    {
        public void AcceptVisitor(IToolsVisitor visitor)
        {
            visitor.VisitPliers(this);
        }
    }
Добавлено через 4 минуты
Круто же? Или так нельзя делать?

Добавлено через 2 минуты
LINQ мне не удобен, объекты лежат в дереве (Это не инструменты, инструменты - для примера)
0
 Аватар для EveKS
601 / 485 / 185
Регистрация: 19.04.2016
Сообщений: 1,885
30.09.2021, 22:04
Зачем весь этот "Визитор", если свелось к...
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
            foreach (var tool in tools)
            {
                tool.AcceptVisitor(inspector);
 
                if (inspector.LastToolType == typeof(Hammer))
                {
                    hammers.Add(inspector.LastHammer);
                }
 
                if (inspector.LastToolType == typeof(Pliers))
                {
                    pliers.Add(inspector.LastPliers);
                }
            }
0
 Аватар для aenye
304 / 186 / 45
Регистрация: 05.07.2018
Сообщений: 580
30.09.2021, 22:05
Цитата Сообщение от Aycon Посмотреть сообщение
Что-то типа
Действительно, хорошее решение, забыл за double dispatch

Цитата Сообщение от Aycon Посмотреть сообщение
Круто же
Только проверка типа все ровно никуда не делась, хотя могла бы

Что если списки заполнять прямо в методах Visit<...>:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    class ToolTypeInspector : IToolsVisitor
    {
        public List<Hammer> Hammers {get;} = new ();
        public List<Plier> Pliers {get;} = new (); 
 
        public void VisitHammer(Hammer hammer)
        {
            Hammers.Add(hammer);
        }
 
        public void VisitPliers(Pliers pliers)
        {
            Pliers.Add(plier);
        }
    }
1
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
30.09.2021, 22:14  [ТС]
Эта проверка безопасна в смысле:
Если есть класс Point и от него наследуется Point2D от которого наследуется Point3D, то прямая проверка на тип объекта Point3D запнётся и даст true ещё на проверке принадлежности классу Point. А Visitor даст однозначный ответ. Согласны?

Добавлено через 45 секунд
Поэтому здесь typeof уместен и однозначен

Добавлено через 1 минуту
Цитата Сообщение от EveKS Посмотреть сообщение
Зачем весь этот "Визитор", если свелось к...
Нет, не свелось, читайте выше. И в том фрагменте, который вы указали, должен был бы быть даункаст

Добавлено через 2 минуты
Можно добавить в Visitor-а обычный enum и проверки вообще не будет. Ща покажу

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class Program
    {
        static void Main(string[] args)
        {
            List<ITool> tools = new List<ITool>()
            {
                new Hammer(),
                new Hammer(),
                new Pliers(),
                new Hammer(),
                new Pliers()
            };
 
            List<Hammer> hammers = new List<Hammer>();
            List<Pliers> pliers = new List<Pliers>();
            ToolTypeInspector inspector = new ToolTypeInspector();
 
            foreach (var tool in tools)
            {
                tool.AcceptVisitor(inspector);
 
                if (inspector.LastToolType == ToolsType.Hammer)
                {
                    hammers.Add(inspector.LastHammer);
                }
 
                if (inspector.LastToolType == ToolsType.Pliers)
                {
                    pliers.Add(inspector.LastPliers);
                }
            }
 
            Console.WriteLine(hammers.Count);
            Console.WriteLine(pliers.Count);
            Console.ReadKey();
        }
    }
 
    interface IToolsVisitor
    {
        void VisitHammer(Hammer hammer);
        void VisitPliers(Pliers pliers);
    }
 
    class ToolTypeInspector : IToolsVisitor
    {
        public ToolsType LastToolType { get; private set; }
 
        public Hammer LastHammer { get; private set; }
        public Pliers LastPliers { get; private set; }
 
        public void VisitHammer(Hammer hammer)
        {
            ClearRestoredTools();
            LastToolType = ToolsType.Hammer;
            LastHammer = hammer;
        }
 
        public void VisitPliers(Pliers pliers)
        {
            ClearRestoredTools();
            LastToolType = ToolsType.Pliers;
            LastPliers = pliers;
        }
 
        void ClearRestoredTools()
        {
            LastHammer = null;
            LastPliers = null;
        }
    }
 
    interface ITool
    {
        void AcceptVisitor(IToolsVisitor visitor);
    }
 
    class Hammer : ITool
    {
        public void AcceptVisitor(IToolsVisitor visitor)
        {
            visitor.VisitHammer(this);
        }
    }
 
    class Pliers : ITool
    {
        public void AcceptVisitor(IToolsVisitor visitor)
        {
            visitor.VisitPliers(this);
        }
    }
 
    enum ToolsType
    {
        Hammer,
        Pliers
    }
0
 Аватар для aenye
304 / 186 / 45
Регистрация: 05.07.2018
Сообщений: 580
30.09.2021, 22:21
Цитата Сообщение от Aycon Посмотреть сообщение
Если есть класс Point и от него наследуется Point2D от которого наследуется Point3D, то прямая проверка на тип объекта
Неа, тогда либо добавлять перегрузку метода визитора для самого последнего типа в цепочке, либо для всей ветки.
0
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
30.09.2021, 22:30  [ТС]
Ну как сказать... Каждый дочерний тип просто перегрузит метод Accept(Visitor visitor), а Visitor укажет конкретный тип. Ну и да, он будет иметь методы
VisitPoint(Point point);
VisitPoint2D(Point2D point);
VisitPoint3D(Point3D point);

Перегрузка не протребовалась бы, полагаю)
Но, спасибо за внимательность

Добавлено через 1 минуту
А, хотя да, вы правы)

Он просто вызовет старший метод...
Но с enum всё работает как положено)
0
 Аватар для aenye
304 / 186 / 45
Регистрация: 05.07.2018
Сообщений: 580
30.09.2021, 22:31
C#
1
2
3
VisitPoint(Point point);
VisitPoint2D(Point2D point);
VisitPoint3D(Point3D point);
Ну это и есть перегрузка, просто обычно название метода не изменяют, и получается что-то вроде
C#
1
2
3
Visit(Point point);
Visit(Point2D point);
Visit(Point3D point);
И в зависимости от посещаемого типа вызывается нужная перегрузка
0
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
06.10.2021, 12:52  [ТС]
Вопрос актуален, я предполагаю, что такой метод нарушает принципы Solid, а именно принцип инверсии зависимостей. При моём решении абстракция ITool зависит от деталей.
Поясняю: ITool зависит от класса IToolVisitor, который в свою очередь, зависит от конкретных классов - Hammer и Pliers. Отсюда следует, что ITool транзитивно зависит от Pliers и Hamer.
Как решить парадокс правильно?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,110
Записей в блоге: 2
07.10.2021, 10:55
Цитата Сообщение от Aycon Посмотреть сообщение
Как решить парадокс правильно?
Треба немного уточнить задание.
1) Каждый тип "инструмента" требует своего ящика или есть какой-то набор ящиков? Во втором случае требуются правила по которым определить ящик для инструмента подходящего для нескольких.
2) Сортировка по ящикам должна происходить автоматически при добавление инструмента или набор ящиков возвращается как результат некоего меттода?

Добавлено через 1 минуту
3) Типы инструментов заранее определены или нужно решение с учётом произвольного их расширения?
0
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
07.10.2021, 11:16  [ТС]
1) У каждого инструмента свой ящик.
2) Нужен способ любой инструмент привести к его самому младшему потомку не нарушая Solid и ООП, не важно каким образом. У вас есть инструмент, вы не знаете наверняка, является ли он молотком и можно ли им стучать, но если это молоток - то забить гвоздь. Как работать с абстракцией ITool не завися от конкретного типа?
3) Нужно решение с учётом произвольного их расширения
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,110
Записей в блоге: 2
07.10.2021, 11:36
Aycon, по интерфейсу Форума:
 Комментарий модератора 
При обращении к другому пользователю указывайте его ник в тегах [NICK][/NICK] или цитируйте часть сообщения на которое отвечаете.
В противном случае ему не придёт уведомление о вашем обращении и вы можете не дождаться ответа на своё сообщение.

Для вставки ника: введите ник, выделите его и нажмите кнопку "Динамик" на панели редактора сообщений.
Или кликните по нику автора сообщения в панели слева от текста его сообщения.

Для вставки цитаты: выделите нужную цитату, должна появиться всплывающая кнопка "Цитировать", нажмите её.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,110
Записей в блоге: 2
07.10.2021, 11:51
Лучший ответ Сообщение было отмечено Aycon как решение

Решение

Цитата Сообщение от Aycon Посмотреть сообщение
2) Нужен способ любой инструмент привести к его самому младшему потомку не нарушая Solid и ООП, не важно каким образом...
Я не способом интересовался, а моментом когда нужно производить сортировку: при вызове метода или автоматические при добавлении инструмента.
Во втором случае нужен для хранения нужен не обычный лист, а коллекция с уведомлением об изменении.
Возможно кастомная.

Добавлено через 7 минут
Цитата Сообщение от Aycon Посмотреть сообщение
1) У каждого инструмента свой ящик.
Тогда только словарь.

Пример получения такого словаря:
C#
1
var boxes = tools.ToLookup(t => t.GetType());
Ключ словаря - тип инструмента.
Значение словаря - список инструментов этого типа.

Добавлено через 3 минуты
Цитата Сообщение от Aycon Посмотреть сообщение
LINQ мне не удобен, объекты лежат в дереве (Это не инструменты, инструменты - для примера)
Чем кастомно "лазить" по всему дереву и "на каждый чих" делать свой метод, лучше создайте один метод возвращающий IEnumerable<Tool> для вашего дерева, его узла или списка узлов.
И потом спокойно пользуйтесь LINQ.
0
13 / 11 / 2
Регистрация: 07.05.2015
Сообщений: 418
07.10.2021, 12:49  [ТС]
Элд Хасп
моментом когда нужно производить сортировку:
Не воспринимайте задачу про инструменты слишком буквально.
Я привёл её в качестве проблемы архитектуры. Проблема в том, что вы не можете абстрагироваться от подклассов какой-нибудь абстракции (коей в данном Примере выступает интерфейс ITool). Я не могу использовать интерфейс вместо классов, его реализующих, поскольку этот интерфейс не декларирует возможности потомков, как в данном случае класс Hammer, который может забивать гвозди, в отличие от Pliers(плоскогубцы). Вопрос чисто фундаментальный, как я могу абстрагировать модуль высокого уровня от модулей низкого уровня через интерфейс, если интерфейс не даёт полного описания объектов, которые его реализуют? Как хорошие программисты решают эту задачу? Я только это хочу знать.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
07.10.2021, 12:49
Помогаю со студенческими работами здесь

При создании статического класса нарушалась вся архитектура приложения
Здравствуйте. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; ...

Архитектура приложения, у кого есть опыт в разработке приложений не в одном файле?
Всем привет! Пишу приложение. Но вот в коде каша. Опыта у меня ещё мало, поэтому события и другие немножко сложные вещи я не...

Архитектура и проектирование приложения: ищу видео, статьи и книги с примерами реальных маленьких приложений
Пытаюсь написать для себя приложение для складского учёта на связке C# + SQLite + Dapper + WinForms. Хочется аналога вот этого, только с...

Архитектура приложения
Приветствую, ребята! Подскажите пожалуйста с архитектурой. Требуется создание что-то вроде каталога с &quot;неограниченным&quot; вложением...

Архитектура приложения
Добрый день, доделываю прогу и пришло время стилизовать приложение под MVC паттерн, а именно убрать мусор из контроллеров. Сейчас это...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
Переходник USB-CAN-GPIO
Eddy_Em 20.03.2026
Достаточно давно на работе возникла необходимость в переходнике CAN-USB с гальваноразвязкой, оный и был разработан. Однако, все меня терзала совесть, что аж 48-ногий МК используется так тупо: просто. . .
Оттенки серого
Argus19 18.03.2026
Оттенки серого Нашёл в интернете 3 прекрасных модуля: Модуль класса открытия диалога открытия/ сохранения файла на Win32 API; Модуль класса быстрого перекодирования цветного изображения в оттенки. . .
SDL3 для Desktop (MinGW): Рисуем цветные прямоугольники с помощью рисовальщика SDL3 на Си и C++
8Observer8 17.03.2026
Содержание блога Финальные проекты на Си и на C++: finish-rectangles-sdl3-c. zip finish-rectangles-sdl3-cpp. zip
Символические и жёсткие ссылки в Linux.
algri14 15.03.2026
Существует два типа ссылок — символические и жёсткие. Ссылка в Linux — это запись в каталоге, которая может указывать либо на inode «файла-ИСТОЧНИКА», тогда это будет «жёсткая ссылка» (hard link),. . .
[Owen Logic] Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ФедосеевПавел 14.03.2026
Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора ВВЕДЕНИЕ Выполняя задание на управление насосной группой заполнения резервуара,. . .
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога Финальные проекты на Си и на C++: hello-sdl3-c. zip hello-sdl3-cpp. zip Результат:
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru