IPB
ЛогинПароль:

> Прочтите прежде чем задавать вопрос!

1. Заголовок темы должен быть информативным. В противном случае тема удаляется ...
2. Все тексты программ должны помещаться в теги [code=pas] ... [/code], либо быть опубликованы на нашем PasteBin в режиме вечного хранения.
3. Прежде чем задавать вопрос, см. "FAQ", если там не нашли ответа, воспользуйтесь ПОИСКОМ, возможно такую задачу уже решали!
4. Не предлагайте свои решения на других языках, кроме Паскаля (исключение - только с согласия модератора).
5. НЕ используйте форум для личного общения, все что не относится к обсуждению темы - на PM!
6. Одна тема - один вопрос (задача)
7. Проверяйте программы перед тем, как разместить их на форуме!!!
8. Спрашивайте и отвечайте четко и по существу!!!

2 страниц V  1 2 >  
 Ответить  Открыть новую тему 
> Объекты... Объекты? Объекты !, (для начинающих)
сообщение
Сообщение #1


Гость






Итак, в этой теме я бы хотел поговорить об объектах, то есть об ООП (Объектно-Ориентированном Программировании). Я не буду опять описывать всю теорию, называть 3 основных принципа ООП, и т.д., это все можно прочитать вот тут: ООП. Объектно-ориентированное программирование , да и в любой книжке по языку программирования. Любому языку программирования.

Здесь я бы хотел поговорить о другом. О решении конкретной задачи с использованием объектов. Причем хотелось бы, чтобы это был не монолог, а диалог. Если вам (а я обращаюсь не только к тем, кто начинает программировать вообще, как выяснилось, даже люди, программирующие достаточно давно, не считают ООП "своей стезёй", эта тема и для них тоже) интересно - я буду продолжать. Если неинтересно - скажите, я продолжать не буду. Я уже когда-то начинал подобную тему, но особого отклика это не получило. Задавайте вопросы, не бойтесь, что они покажутся странными, простыми и т.д. Здесь важно разобраться во всех мелочах.

Для начала я бы хотел взять вот такую задачу (это - реальное условие, в которое я внес небольшие изменения): "Написать программу (работающую в диалоговом режиме), которая осуществляет взаимодействие пользователя с одной из трех ДСД - динамических структур данных - Список, Стек, Бинарное дерево, и позволяет:
1) добавлять элементы в структуру
2) удалять элементы из структуры
3) отображать текущее содержимое выбранной структуры данных на экране".

Ну, для начала, "а почему именно ООП?" спросите вы. И отчасти будете правы, поскольку эту программу (как и все остальные, впрочем) можно написать, используя процедурное программирование. Отвечаю smile.gif :
1) Если б я хотел, чтоб это было написано без ООП, я бы не начинал эту тему (или не включил бы сюда эту задачу), в оригинальном задании так и было сказано: "... объектно-ориентированную программу..."
2) решить задачу с использованием только процедур/функций, конечно, можно, но такое решение будет а) более объемным, чем с использованием объектов; б) менее расширяемым

Есть еще третья причина по которой я бы советовал сделать "это" с использованием объектов: в результате вы получите типы "Стек", "Список", "Дерево", которые будут максимально независимы от окружения, от остальной части программы, и их можно будет с успехом применять в других проектах.

Итак, задача поставлена, можно начинать. Для начала - о главном, о том, с чего начинается проектирование программы: о ее структуре, о том, какие объектные типы (и как именно) будут описаны в программе. Так называемые "заглушки", дающие представление об общей структуре, но пока не касающиеся реализации... Многие из начинающих быстро сделают так:
type
TList = object
{ тут реализация списка }
end;
TStack = object
{ тут реализация стека }
end;
TTree = object
{ тут реализация дерева }
end;

Var
myList: TList;
myStack: TStack;
myTree: TTree;

{ ... }
WriteLn('С какой структурой будете работать? 1) List 2) Stack 3) Tree');
ReadLn(ch);
Case ch of
'1':
begin
{ работа со списком через myList }
end;

'2':
begin
{ работа со стеком через myStack }
end;

'3':
begin
{ работа с деревом через myTree }
end;
end;
Откуда, собственно, первый вопрос: А хорошо ли делать так, как я показал выше? Какие у этого способа вы видите недостатки, какие преимущества? Как бы вы посоветовали мне сделать это по-другому? (Учтите, написание хорошей программы - это сложный процесс, иногда приходится переписывать все почти с нуля, если сразу ошибся при проектировании, поэтому важно с самого начала спроектировать правильно, и только потом приступать к реализации, а не так, как делают очень многие - сначала нарисуют окошечки-рюшечки, а потом туда пытаются втиснуть, собственно, основную часть задания...)

P.S. Если это задание вам кажется неподходящим, предлагайте свои варианты, можете взять любое реальное задание с форума со ссылкой на него (только не надо давать здесь свое задание, в надежде, что вам тут его решат полностью. Может, и решат, но времени это займет достаточно много smile.gif )

P.P.S. Свои ответы скрывайте тегами [SPОILER][/SPОILER] (все буквы в названии тегов должны быть латинские), чтоб другие тоже могли подумать самостоятельно, не видя вашего ответа...
 К началу страницы 
+ Ответить 
сообщение
Сообщение #2


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


О, хорошая тема. Советы от гуру =)
Спойлер (Показать/Скрыть)


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #3


Гость






Archon, можешь показать (псевдо)кодом первый и второй путь, и что именно тебе мешает в первом случае использовать полиморфизм, а во втором - повторно использовать код? Я например таких проблем не вижу...

Цитата
Возможно, имеет смысл...
Возможно, я поэтому и спросил, какие недостатки есть у вышеприведенной схемы, чего нельзя (или сложно) сделать с её помощью...
 К началу страницы 
+ Ответить 
сообщение
Сообщение #4


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


Вот что я имел ввиду:
Прикрепленное изображение
В первом случае непонятно, как написать функцию, способную принимать стек (к примеру) независимо от его реализации.

Во втором случае реализация пишется отдельно для стека, очереди и списка.

Спойлер (Показать/Скрыть)


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #5


Гость






Цитата
Во втором случае реализация пишется отдельно для стека, очереди и списка.
Естественно, если ты хочешь написать ее отдельно (причем с нуля) для очереди и для стека - то ты и будешь ее писать с нуля. Я в таких случаях делаю так:
Спойлер (Показать/Скрыть)

P.S. Спойлером - чтоб не сбивать своим решением никого, может еще кто-нибудь что-то предложит...
 К началу страницы 
+ Ответить 
сообщение
Сообщение #6


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


Цитата
Я в таких случаях делаю так:

Ага, я что-то такое себе представлял smile.gif А если добавить интерфейсы (имеется ввиду interface, как конструкция языка)? Тогда может лучше будет сделать 2 дерева наследования:
1 Дерево интерфейсов для собственно интерфейсов (черт, почему это не 2 разных слова smile.gif)
2 Дерево классов для реализации (как в моем способе 1). При этом классы реализуют интерфейсы, через которые и идет полиморфизм.


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #7


Гость






Цитата
А если добавить интерфейсы (имеется ввиду interface, как конструкция языка)
Это не даст никакого выигрыша, вот множественное наследование (в стиле С++) дало бы, а с интерфейсами - нет... Ну, разве что... для того, чтобы гарантировать присутствие нужного тебе метода в классе.
 К началу страницы 
+ Ответить 
сообщение
Сообщение #8


Профи
****

Группа: Пользователи
Сообщений: 865
Пол: Мужской
Реальное имя: Вячеслав

Репутация: -  20  +


type
tbase = object
end;

dds_first = object
procedure PutFirst(var value: T);
procedure GetFirst(var value: T);
procedure PutLast(var value: T);
procedure GetLast(var value: T);
end;

tstack = object(tbase)
end;
tstack_first = object(tstack)
private
inner: dds_first;
end;

tqueue = object(tbase)
end;
tqueue_first = object(tqueue)
private
inner: dds_first;
end;
tlist = object(tbase)
end;
tlist_first = object(tlist)
private
inner: dds_first;
end;
А зачем нужны пустые объекты? И обязательно в private надо добавлять?

Сообщение отредактировано: Client -
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #9


Гость






Цитата
А зачем нужны пустые объекты?
Это не полное описание объектов... Только иерархия. Естественно, что:

  tstack = object(tbase)
procedure Push(value: T); virtual; abstract;
procedure Pop(var value: T); virtual; abstract;
end;
tstack_first = object(tstack)
{ здесь - конструктор/деструктор }
procedure Push(value: T); virtual;
procedure Pop(var value: T); virtual;
private
inner: dds_first;
end;
procedure tstack_first.push(value: T);
begin inner.put_first(value); end;
procedure tstack_first.pop(var value: T);
begin inner.get_first(value); end;
, теперь для реализации полиморфной процедуры, работающей со стеком, достаточно сделать:
procedure P(var stack: tstack);
begin
{ ... }
end;
, и передавать в нее можно будет любого наследника TStack, то есть, стек с любой реализацией...

Цитата
И обязательно в private надо добавлять?
Желательно. Чем меньше возможностей извне добраться до внутреннего представления данных - тем лучше. Инкапсуляция.
 К началу страницы 
+ Ответить 
сообщение
Сообщение #10


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


Цитата
Это не даст никакого выигрыша, вот множественное наследование (в стиле С++) дало бы, а с интерфейсами - нет...
Это потому что полиморфизм через интерфейсы в делфи работают медленно?


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #11


Гость






Это потому, что работа с Interface-ами ЗАМЕНЯЕТ, а не реализует multiple inheritance... Вот тебе код:
{$mode delphi}
type
IMyStack = interface(iunknown)
procedure push(value: integer);
end;

tmybase = class(tinterfacedobject)
end;

type
tmystack = class(tmybase, imystack)
procedure push(value: integer);
end;

procedure tmystack.push(value: integer);
begin
writeln('tstack.push: ', value);
end;

var
s: tmystack;
i: integer;
begin
s := tmystack.create;
s.push(10);
end.
, он запускается и работает. А теперь попробуй переписать его так, чтобы не было необходимости дублировать прототипы методов в классе Tmystack, чтоб метод, раз описанный в Imystack (да, да, меня не интересует реализация в моем классе, это убивает смысл того, что было задумано, меня интересует реализация нужного мне метода в одном из предков так, чтобы мой класс наследовал все, что было во всех предках), можно было когда нужно унаследовать и использовать, как это делается в С++ (С++ный код привести?). Функциональность не должна пострадать... Вот если тебе удастся это сделать - продолжим разговор о такой реализации...
 К началу страницы 
+ Ответить 
сообщение
Сообщение #12


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


Не зачем наследовать функциональность от всех предков. Достаточно обеспечить полиморфизм для классов из разных ветвей дерева наследования (или вовсе разных деревьев). Вот схема:
Прикрепленное изображение
Использовать так:
var
MyStack: IStack;
begin
MyStack := TStack1.Create; { <- тут любая реализация стека }
{ работаем со стеком }
end;
Получаем следующие преимущества:
1 Можем наследовать реализации как угодно и от чего угодно - пользователю классов это до лампочки, он работает с интерфейсами. В итоге если функция ждет на вход IStack можем пихать туда любую реализацию, лишь бы поддерживался соответствующий интерфейс.
2 Нет такого вот:
procedure tstack_first.push(value: T);
begin inner.put_first(value); end;
3 Обычно, когда пользователь смотрит на класс, он видит не только то, что его интересует (public-методы и свойства), но и информацию, касающуюся реализации: секции private/protected, "read ... write ..." в свойствах, а в некоторых языках (C#, Java) - код реализации всех методов. Конечно, не бог весть какая неприятность, но если показывать юзеру класса интерфейс - он видит только то, что нужно для его использования. Это как-то эстетичнее, что-ли.

PS Вышесказанное - даже не мнение, а только попытка его сформировать.


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #13


Гость






Цитата
Вот схема:
Бла-бла-бла... На уровне схемы все выглядит прекрасно. Я дал тебе код, приведи и ты РАБОТАЮЩИЙ код, построенный по твоей схеме... А схемы - как полететь на Луну на чайнике - я тоже могу составлять, и все будет выглядеть идеально...

Цитата
Не зачем наследовать функциональность от всех предков.
Угу, угу... А зачем, извини, в таком случае вообще наследоваться от чего-то, если функциональность не наследуется?

Кроме всего прочего: попробуй твоей схемой "на лету" изменить реализацию, я хочу это видеть. У меня это делается на ура, просто сам объект типа dds_first меняется на "указатель на корень дерева наследования", и когда надо - убивается старый p_inner, и инициализируется новый, нужного типа. Только не надо про то, что оно не используется, и не надо. Не используется - потому что не реализуете так, все в интерфейсы лезете... Мной - очень даже используется...
 К началу страницы 
+ Ответить 
сообщение
Сообщение #14


Профи
****

Группа: Пользователи
Сообщений: 865
Пол: Мужской
Реальное имя: Вячеслав

Репутация: -  20  +


Только не закрывайте тему ПЛИИИИЗ
щас время маловато, экзамен сдам по ООП (теорию повторю, чтоб не задавать глупых вопросов) и присоединюсь по плотному smile.gif

Сообщение отредактировано: Client -
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #15


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


Цитата
Я дал тебе код, приведи и ты РАБОТАЮЩИЙ код, построенный по твоей схеме...
{$mode objfpc}

type
IStack = interface
procedure Push(Value: Integer);
end;

TMyBase = class(TInterfacedObject)
end;

TImplementation1 = class(TMyBase)
protected
Data: Integer;
procedure SaveData(Value: Integer);
end;

TImplementation2 = class(TMyBase)
protected
procedure WriteData(Value: Integer);
end;

TStack1 = class(TImplementation1, IStack)
public
procedure Push(Value: Integer);
end;

TStack2 = class(TImplementation2, IStack)
public
procedure Push(Value: Integer);
end;

procedure TImplementation1.SaveData(Value: Integer);
begin
Data := Value;
WriteLn('Saved...');
end;

procedure TImplementation2.WriteData(Value: Integer);
begin
WriteLn(Value);
end;

procedure TStack1.Push(Value: Integer);
begin
SaveData(Value);
end;

procedure TStack2.Push(Value: Integer);
begin
WriteData(Value);
end;

var
Stack: IStack;
begin
Stack := TStack1.Create;
Stack.Push(10);
Stack := TStack2.Create;
Stack.Push(10);
end.
Да, насчет пункта 2 я погорячился rolleyes.gif.
Цитата
Угу, угу... А зачем, извини, в таком случае вообще наследоваться от чего-то, если функциональность не наследуется?
Я уже много раз упоминал слово полиморфизм. Ради него же.
Цитата
Кроме всего прочего: попробуй твоей схемой "на лету" изменить реализацию, я хочу это видеть.
Никак. Действительно, недостаток. Не приходило в голову, спасибо. Хотя вот это:
	Stack := TStack1.Create;
{ используем }
Stack := TStack2.Create;
можно назвать сменой реализации? Если нужно при этом сохранить какие-то данные, то
{ . . . }

TStack2 = class(TImplementation2, IStack)
public
procedure Push(Value: Integer);
constructor Create(OldStack: TStack1);
end;

{ . . . }

constructor TStack2.Create(OldStack: TStack1);
begin
Data := OldStack.Data;
end;

var
S1: TStack1;
S2: TStack2;
Stack: IStack;
begin
S1 := TStack1.Create;
Stack := S1;
Stack.Push(10);

S2 := TStack2.Create(S1);
Stack := S2;
Stack.Push(10);

S1.Destroy;
S2.Destroy;
end.
Как думаешь, ересь? unsure.gif

Сообщение отредактировано: Archon -


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #16


Гость






Цитата
Как думаешь, ересь?
Я б так делать не стал, это все, что я могу тебе сказать. Ересь или нет - это ты решай сам, я не могу тебе говорить, что и как делать. НО... То, что у тебя начиная с какого-то момента времени существует 2 экземпляра Стека - уже наводит на некоторые размышления. Их не должно быть, потому что все, что я хотел - это сменить реализацию, а это достигается с помощью "pImpl idiom" - того способа, который я тебе показал... Все делается внутри одного экземпляра.

Ну и то, что эта идиома работает начиная с TP версии 5.5, а не с Object Pascal-я - тоже склоняет чашу весов в ее сторону (для меня, по крайней мере). Вот так выглядит основная часть программы, написанной с использованием моего способа:
var
stack: TMyStack;
i: integer;

begin
stack.Create(new(PTImplArray, init));
for i := 1 to 10 do stack.Push(3 * i);

for i := 1 to 3 do writeln(stack.pop);
stack.change_impl(new(PTImplDynamic, init));
for i := 1 to 7 do writeln(stack.pop);

stack.Destroy;
end.

Как видишь, никаких лишних экземпляров, ничего подозрительного...

Кстати, еще один минус твоего кода: если тебе понадобится сменить "реализацию" с TImplementation2 на TImplementation1 - будешь добавлять еще один метод? А если реализаций не 2, а 4? smile.gif В моем случае - хоть заизменяйся, как только появилась единственная реализация change_impl, можно менять все что угодно, в любую сторону... Ничего больше добавлять не надо, можно запихать TMyStack в модуль, и забыть о том, как и что там делается, и потом добавлять только наследников TImpl... Реализацию change_impl пока не привожу, попробуй подумать, как вообще можно это сделать yes2.gif
 К началу страницы 
+ Ответить 
сообщение
Сообщение #17


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


Цитата
То, что у тебя начиная с какого-то момента времени существует 2 экземпляра Стека - уже наводит на некоторые размышления. Их не должно быть, потому что все, что я хотел - это сменить реализацию, а это достигается с помощью "pImpl idiom" - того способа, который я тебе показал... Все делается внутри одного экземпляра.
Однако внутри экземпляра происходит тоже самое - удаление старого экземпляра PImpl и создание нового. Хотя то, что это происходит внутри - уже плюс.
Цитата
Кстати, еще один минус твоего кода: если тебе понадобится сменить "реализацию" с TImplementation2 на TImplementation1 - будешь добавлять еще один метод? А если реализаций не 2, а 4? В моем случае - хоть заизменяйся, как только появилась единственная реализация change_impl, можно менять все что угодно, в любую сторону...
В твоем случае можно свободно менять реализации только если интерфейсы у них одинаковые и наследуются от общего предка. У меня даже способы работы с реализациями различны (я специально писал код, чтобы показать такую возможность). Правда не представляю пока где это может понадобиться, так что вряд ли это действительно "фича".
Пожалуй, твой способ все же правильней. Спасибо, что помог разобраться. good.gif


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #18


Гость






Цитата
Правда не представляю пока где это может понадобиться, так что вряд ли это действительно "фича".
Если ты внимательно читал мои ответы в теме про Doomed Game (или это было в ЛС, не помню, но где-то я уже говорил об этом), я приводил пример, когда вот такое вот изменение реализации "на лету" может значительно облегчить задачу... Если что, оно относилось к AI (к задаче, решаемой юнитом в данный момент: движется себе модуль, движется, вдруг бац, атака на него. Что делать? А ничего. Реализация "Дозор" удаляется, вместо нее инициализируется реализация "Оборона", и начинаем битву. Закончили обороняться - возвращаемся к предыдущему состоянию. Просто и эффективно...). Опять "идиома pImpl", то есть, она таки имеет право на существование smile.gif Или тебя интересует пример, касающийся именно структур данных? Так его, я думаю, тоже можно будет привести...

Но что-то мне подсказывает, что новички все-же не очень хотят присоединяться, обсуждение ушло опять далеко от начального уровня. unsure.gif Ну, пока тема пускай повисит, Client обещал присоединиться...

Кстати, Archon, проект все-таки заглох окончательно?
 К началу страницы 
+ Ответить 
сообщение
Сообщение #19


Профи
****

Группа: Пользователи
Сообщений: 618
Пол: Мужской

Репутация: -  24  +


Да, без тебя заглох =). Я было пытался продолжать в одиночку, но энтузиазм угас. Главное, он не прошел бесследно. Все, что я знаю об объектах, я знаю благодаря этому проекту. Кроме того, он помог окончательно перейти на 32-битные компиляторы и научиться работать с OpenGL и DirectX. Еще навыки проектирования структуры приложений получил какие-никакие. Есть у меня пара тестов старых. Там структура проекта прослеживается (с кучей заглушек) и немного графики. Могу показать, конечно, но до самого интересного (ИИ) дело не дошло даже близко.

Сообщение отредактировано: Archon -


--------------------
Close the World...txeN eht nepO
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 
сообщение
Сообщение #20


Профи
****

Группа: Пользователи
Сообщений: 865
Пол: Мужской
Реальное имя: Вячеслав

Репутация: -  20  +


Тему несколько раз читал, но не очень понял, поэтому начну с начала smile.gif
Цитата
А хорошо ли делать так, как я показал выше? Какие у этого способа вы видите недостатки, какие преимущества?
Хм, даже не знаю, описал объект и его обработку и работаешь с ним-ничто не мешает. Т.е. ответа я не знаю smile.gif
Допустим есть объект с полем х и его потомок. Могу ли я переопределить поле х в потомке (метод можно, а поле)?
 Оффлайн  Профиль  PM 
 К началу страницы 
+ Ответить 

2 страниц V  1 2 >
 Ответить  Открыть новую тему 
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 





- Текстовая версия 23.10.2020 7:16
500Gb HDD, 6Gb RAM, 2 Cores, 7 EUR в месяц — такие хостинги правда бывают
Связь с администрацией: bu_gen в домене octagram.name