В некотором смысле по мотивам:
Коротко. О фабриках
Маниловщина. Пишем реализацию IStorage применяя TDD
О чём хочу повести речь?
У нас своя есть реализация IStorage (ну и IStream соответственно).
Она - хороша. В некотором смысле. Хотя бы потому, что достаточно стабильно работает уже лет 15-ть.
Но там есть "некоторые проблемы".
И я с этими проблемами сейчас скурпулёзно разбираюсь.
Ну там "проблемы" сложного порядка в гетерогенной сетевой среде.
Я не о них.
Но есть и "локальные проблемы".
Например то, что там всё построено на "бинарной сериализации".
Т.е. сделано примерно так:
"Реальный код" можно посмотреть тут.
В чём проблема? А в том, что "формат" TStoreHeader "просто так" не изменишь. Потому, что "всё поедет".
Что делать?
Для начала вот примерно так:
Какой следующий шаг?
Вот как-то так:
Что мы тут сделали?
Мы ввели базовый абстрактный класс - TStoreHeaderAbstract и фабрику -TStoreHeaderFactory.
Теперь как нам поменять формат данных?
А вот примерно так:
Более того можно тогда написать и так:
Что мы тут сделали?
Мы во-первых - разбили "бинарную сериализацию" записи на несколько "бинарных сериализаций" отдельных полей.
А во-вторых - мы поменяли часть полей местами, чтобы продемонстрировать "суть подхода".
За рамками повествования конечно осталось много вопросов.
Например - "откуда берётся GetVersionGUID"?
Или - "что делать, если запись версии изначально не предусмотрены"?
Это важные вопросы. Но они "не влезают в рамки" данного поста. Да и вообще говоря - они важные, но "достаточно технические". Если будет интерес - я и их подробнее разберу.
Но пока - оставлю их "за рамками повествования".
Что в итоге?
В итоге - по-моему - было показано как фабрики являются весомым дополнением к инкапсуляции и полиморфизму.
Мы сначала воспользовались полиморфизмом - введя тип TStoreHeaderAbstract.
А потом воспользовались инкапсуляцией - разделив TStoreHeader.Data и TStoreHeaderNew.Data.
Ну и за счёт полиморфиза и инкапсуляции мы в некотором роде ушли от "бинарной сериализации".
Почему?
Потому, что следующий шаг может быть таким:
- т.е. тут уже пишем/читаем не "бинарно", а так как написано в SomeOtherDataType.Load/SomeOtherDataType.Save.
Итак.
Что я хотел показать?
Я повторю.
Я хотел показать, что фабрики являются весомым дополнением к инкапсуляции и полиморфизму.
(Но фабрики, скажем так - это "полиморфизм в квадрате". Потому, что полиморфизм "начинает действовать" ещё до создания экземпляра объекта, до создания экземпляра объекта может работать полиморфизм фабрики. О полиморфных фабриках стоит написать?)
Как уж это у меня получилось - судить вам.
Думаю - "Америку не открыл", но надеюсь, что написал что-то полезное.
Коротко. О фабриках
Маниловщина. Пишем реализацию IStorage применяя TDD
О чём хочу повести речь?
У нас своя есть реализация IStorage (ну и IStream соответственно).
Она - хороша. В некотором смысле. Хотя бы потому, что достаточно стабильно работает уже лет 15-ть.
Но там есть "некоторые проблемы".
И я с этими проблемами сейчас скурпулёзно разбираюсь.
Ну там "проблемы" сложного порядка в гетерогенной сетевой среде.
Я не о них.
Но есть и "локальные проблемы".
Например то, что там всё построено на "бинарной сериализации".
Т.е. сделано примерно так:
type TStoreHeader = record rNextPosition : Int64; rRealSize : Int64; ... end;//TStoreHeader ... procedure SomeReadCode; var l_H : TStoreHeader; begin ... Stream.Read(l_H, SizeOf(l_H); ... end; ... procedure SomeWriteCode; var l_H : TStoreHeader; begin ... Stream.Write(l_H, SizeOf(l_H); ... end;
"Реальный код" можно посмотреть тут.
В чём проблема? А в том, что "формат" TStoreHeader "просто так" не изменишь. Потому, что "всё поедет".
Что делать?
Для начала вот примерно так:
type TStoreHeaderRec = record rNextPosition : Int64; rRealSize : Int64; ... end;//TStoreHeaderRec TStoreHeader = class private Data : TStoreHeaderRec; public procedure Load(aStream: TStream); procedure Save(aStream: TStream); end;//TStoreHeader ... procedure TStoreHeader.Load(aStream: TStream); begin aStream.ReadBuffer(Data, SizeOf(Data); end; procedure TStoreHeader.Save(aStream: TStream); begin aStream.WriteBuffer(Data, SizeOf(Data); end; ... procedure SomeReadCode; var l_H : TStoreHeader; begin ... l_H := TStoreHeader.Create; ... l_H.Load(Stream); ... end; ... procedure SomeWriteCode; var l_H : TStoreHeader; begin ... l_H := TStoreHeader.Create; ... l_H.Save(Stream); ... end;
Какой следующий шаг?
Вот как-то так:
type TStoreHeaderAbstract = class public procedure Load(aStream: TStream); virtual; abstract; procedure Save(aStream: TStream); virtual; abstract; end;// TStoreHeaderAbstract ... TStoreHeaderRec = record rNextPosition : Int64; rRealSize : Int64; ... end;//TStoreHeaderRec TStoreHeader = class(TStoreHeaderAbstract) private Data : TStoreHeaderRec; public procedure Load(aStream: TStream); override; procedure Save(aStream: TStream); override; end;//TStoreHeader TStoreHeaderFactory = class public class function Make: TStoreHeaderAbstract; end;//TStoreHeaderFactory ... class function TStoreHeaderFactory.Make: TStoreHeaderAbstract; begin Result := TStoreHeader.Create; end; procedure TStoreHeader.Load(aStream: TStream); begin aStream.ReadBuffer(Data, SizeOf(Data); end; procedure TStoreHeader.Save(aStream: TStream); begin aStream.WriteBuffer(Data, SizeOf(Data); end; ... procedure SomeReadCode; var l_H : TStoreHeaderAbstract; begin ... l_H := TStoreHeaderFactory.Make; ... l_H.Load(Stream); ... end; ... procedure SomeWriteCode; var l_H : TStoreHeaderAbstract; begin ... l_H := TStoreHeaderFactory.Make; ... l_H.Save(Stream); ... end;
Что мы тут сделали?
Мы ввели базовый абстрактный класс - TStoreHeaderAbstract и фабрику -TStoreHeaderFactory.
Теперь как нам поменять формат данных?
А вот примерно так:
type TStoreHeaderAbstract = class public procedure Load(aStream: TStream); virtual; abstract; procedure Save(aStream: TStream); virtual; abstract; end;// TStoreHeaderAbstract ... TStoreHeaderRec = record rNextPosition : Int64; rRealSize : Int64; ... end;//TStoreHeaderRec TStoreHeader = class(TStoreHeaderAbstract) private Data : TStoreHeaderRec; public procedure Load(aStream: TStream); override; procedure Save(aStream: TStream); override; end;//TStoreHeader TStoreHeaderRecNew = record rNextPosition : Int64; rRealSize : Int64; rSomeOtherData : SomeOtherType; ... end;//TStoreHeaderRecNew TStoreHeaderNew = class(TStoreHeaderAbstract) private Data : TStoreHeaderRecNew; public procedure Load(aStream: TStream); override; procedure Save(aStream: TStream); override; end;//TStoreHeaderNew TStoreHeaderFactory = class public class function Make(aVersion : TGUID): TStoreHeaderAbstract; end;//TStoreHeaderFactory ... class function TStoreHeaderFactory.Make(aVersion : TGUID): TStoreHeaderAbstract; begin if EqualGUID(aVersion, OldFormatGUID) then Result := TStoreHeader.Create else if EqualGUID(aVersion, NewFormatGUID) then Result := TStoreHeaderNew.Create else Assert(false, 'Неверный заголовок'); end; procedure TStoreHeader.Load(aStream: TStream); begin aStream.ReadBuffer(Data, SizeOf(Data); end; procedure TStoreHeader.Save(aStream: TStream); begin aStream.WriteBuffer(Data, SizeOf(Data); end; ... procedure TStoreHeaderNew.Load(aStream: TStream); begin aStream.ReadBuffer(Data, SizeOf(Data); end; procedure TStoreHeaderNew.Save(aStream: TStream); begin aStream.WriteBuffer(Data, SizeOf(Data); end; ... procedure SomeReadCode; var l_H : TStoreHeaderAbstract; begin ... l_H := TStoreHeaderFactory.Make(GetVersionGUID); ... l_H.Load(Stream); ... end; ... procedure SomeWriteCode; var l_H : TStoreHeaderAbstract; begin ... l_H := TStoreHeaderFactory.Make(GetVersionGUID); ... l_H.Save(Stream); ... end;
Более того можно тогда написать и так:
... procedure TStoreHeaderNew.Load(aStream: TStream); begin aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize); aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition); aStream.ReadBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData); end; procedure TStoreHeaderNew.Save(aStream: TStream); begin aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize); aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition); aStream.WriteBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData); end;
Что мы тут сделали?
Мы во-первых - разбили "бинарную сериализацию" записи на несколько "бинарных сериализаций" отдельных полей.
А во-вторых - мы поменяли часть полей местами, чтобы продемонстрировать "суть подхода".
За рамками повествования конечно осталось много вопросов.
Например - "откуда берётся GetVersionGUID"?
Или - "что делать, если запись версии изначально не предусмотрены"?
Это важные вопросы. Но они "не влезают в рамки" данного поста. Да и вообще говоря - они важные, но "достаточно технические". Если будет интерес - я и их подробнее разберу.
Но пока - оставлю их "за рамками повествования".
Что в итоге?
В итоге - по-моему - было показано как фабрики являются весомым дополнением к инкапсуляции и полиморфизму.
Мы сначала воспользовались полиморфизмом - введя тип TStoreHeaderAbstract.
А потом воспользовались инкапсуляцией - разделив TStoreHeader.Data и TStoreHeaderNew.Data.
Ну и за счёт полиморфиза и инкапсуляции мы в некотором роде ушли от "бинарной сериализации".
Почему?
Потому, что следующий шаг может быть таким:
... procedure TStoreHeaderNew.Load(aStream: TStream); begin aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize); aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition); Data.rSomeOtherData.Load(aStream); end; procedure TStoreHeaderNew.Save(aStream: TStream); begin aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize); aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition); Data.rSomeOtherData.Save(aStream); end;
- т.е. тут уже пишем/читаем не "бинарно", а так как написано в SomeOtherDataType.Load/SomeOtherDataType.Save.
Итак.
Что я хотел показать?
Я повторю.
Я хотел показать, что фабрики являются весомым дополнением к инкапсуляции и полиморфизму.
(Но фабрики, скажем так - это "полиморфизм в квадрате". Потому, что полиморфизм "начинает действовать" ещё до создания экземпляра объекта, до создания экземпляра объекта может работать полиморфизм фабрики. О полиморфных фабриках стоит написать?)
Как уж это у меня получилось - судить вам.
Думаю - "Америку не открыл", но надеюсь, что написал что-то полезное.
Комментариев нет:
Отправить комментарий