По мотивам:
Коротко. Про IStorage
Про "рефакторинг"
Коротко. Про контроль типов
Пусть у нас есть ресурс. Например файл, который "часто" открывается на чтение.
Ну бизнес-логика - "так устроена".
Ну как-то так:
И мы (вдруг) понимаем, что "открытие файла" (в главном потоке) это - "бутылочное горлышко".
(Это - надуманный пример, но он "близок к жизни")
Как можно "улучшить ситуацию"?
Ну для начала сделаем так:
-- что мы тут сделали?
Да пока - "ничего", просто заменили конструктор, на фабрику.
Что можно сделать дальше?
Ну например можно попробовать "собрать статистику" (потокозащищённость - оставлю "за скобками"):
-- что мы тут сделали?
А мы тут посчитали число открытий конкретного файла в основном потоке.
Зачем?
Пойдём дальше.
Пусть у нас есть "оценка" - cTooMuchOpened.
Взятая "с потолка", точнее из "практики".
Что можно сделать?
Ну сделаем примерно так:
-- что мы тут сделали?
А тут мы научились "понимать", что какие-то файлы "слишком много открываются".
Что будем делать дальше?
Попробуем "закешировать" эти файлы. Т.е. "объекты их реализующие".
Ну как-то так:
-- что мы тут сделали?
А мы тут устроили кеш файлов. Т.е. не открываем их повторно, а получаем из кеша.
Повторю - пример надуманый, но "почти из жизни".
Какие есть проблемы?
А например такие, что "просто так DeleteFile уже сложно позвать".
Почему?
А потому, что он "может быть открыт".
Ну и "смежные проблемы".
Которые можно решить примерно так:
Звать не:
а:
-- но это уже - "тонкости", хотя и небезпроблемные.
Ну вот собственно и всё, что я хотел ещё сказать о фабриках.
Понимаю - "слом шаблона".
Но может быть - кому-нибудь понравится.
P.S. Может быть какие-то запятые или пробелы стоя не так как хотелось бы. Но я ЧЕСТНО СТАРАЛСЯ. Хотя и писал "с листа". И код - не компилировал. Хотел лишь идею проиллюстрировать.
P.P.S. Если кто-то захочет спросить про SHARE_READ или SHARE_WRITE - скажу - "это правильный ход мыслей", но тут же процитирую одного своего педагога, который на вопрос "почему мы берём тангенс бесконечно малого, а не само бесконечно малое" ответил - "давайте на глупые вопросы Саши Люлина не будем тратить время".
Коротко. Про IStorage
Про "рефакторинг"
Коротко. Про контроль типов
Пусть у нас есть ресурс. Например файл, который "часто" открывается на чтение.
Ну бизнес-логика - "так устроена".
Ну как-то так:
type TReadFile = class(TInterfacedObject, IStream) public constructor Create(const aFileName: String); end;//TReadFile ... var l_F : IStream; begin l_F := TReadFile.Create('aFileName'); end;
И мы (вдруг) понимаем, что "открытие файла" (в главном потоке) это - "бутылочное горлышко".
(Это - надуманный пример, но он "близок к жизни")
Как можно "улучшить ситуацию"?
Ну для начала сделаем так:
type TReadFile = class(TInterfacedObject, IStream) public constructor Create(const aFileName: String); class function Make(const aFileName: String): IStream; end;//TReadFile ... class function TReadFile.Make(const aFileName: String): IStream; begin Result := Create(aFileName); end; ... var l_F : IStream; begin l_F := TReadFile.Make('aFileName'); end;
-- что мы тут сделали?
Да пока - "ничего", просто заменили конструктор, на фабрику.
Что можно сделать дальше?
Ну например можно попробовать "собрать статистику" (потокозащищённость - оставлю "за скобками"):
type TReadFileInfo = record public rFileName : String; rOpenCount : String; constructor Create(const aFileName: String); end;//TReadFileInfo TReadFileInfoList = class(TList<TReadFileInfo>) protected procedure IncOpenCount(const aFileName: String); end;//TReadFileInfoList TReadFile = class(TInterfacedObject, IStream) private class var f_ReadFileInfoList : TReadFileInfoList; protected class constructor Create; class destructor Destroy; public constructor Create(const aFileName: String); class function Make(const aFileName: String): IStream; end;//TReadFile ... constructor TReadFileInfo.Create(const aFileName: String); begin inherited; rFileName := aFileName; rOpenCount := 0; end; ... procedure TReadFileInfoList.IncOpenCount(const aFileName: String); var l_Index : Integer; l_Item : TReadFileInfo; begin for l_Index := 0 to Pred(Count) do begin l_Item := Items[l_Index]; if (l_Item.rFileName = aFileName) then begin Inc(l_Item.rOpenCount); Items[l_Index] := l_Item; Exit; end;//l_Item.rFileName = aFileName Add(TReadFileInfo.Create(aFileName)); end;//for l_Index end; ... class constructor TReadFileInfoList.Create; begin inherited; f_ReadFileInfoList := TReadFileInfoList.Create; end; class destructor TReadFileInfoList.Destroy; begin FreeAndNil(f_ReadFileInfoList); inherited; end; ... class function TReadFile.Make(const aFileName: String): IStream; begin if (CurrentTreadID = MainThreadID) then f_ReadFileInfoList.IncOpenCount(aFileName); Result := Create(aFileName); end; ... var l_F : IStream; begin l_F := TReadFile.Make('aFileName'); end;
-- что мы тут сделали?
А мы тут посчитали число открытий конкретного файла в основном потоке.
Зачем?
Пойдём дальше.
Пусть у нас есть "оценка" - cTooMuchOpened.
Взятая "с потолка", точнее из "практики".
Что можно сделать?
Ну сделаем примерно так:
type TReadFileInfo = record public rFileName : String; rOpenCount : String; constructor Create(const aFileName: String); end;//TReadFileInfo TReadFileInfoList = class(TList<TReadFileInfo>) protected pocedure IncOpenCount(const aFileName: String; out theTooMuchIndex: Integer); end;//TReadFileInfoList TReadFile = class(TInterfacedObject, IStream) private class var f_ReadFileInfoList : TReadFileInfoList; protected class constructor Create; class destructor Destroy; public constructor Create(const aFileName: String); class function Make(const aFileName: String): IStream; end;//TReadFile ... constructor TReadFileInfo.Create(const aFileName: String); begin inherited; rFileName := aFileName; rOpenCount := 0; end; ... const cTooMuchOpened = 1000; procedure TReadFileInfoList.IncOpenCount(const aFileName: String; out theTooMuchIndex: Integer); var l_Index : Integer; l_Item : TReadFileInfo; begin theTooMuchIndex := -1; for l_Index := 0 to Pred(Count) do begin l_Item := Items[l_Index]; if (l_Item.rFileName = aFileName) then begin Inc(l_Item.rOpenCount); Items[l_Index] := l_Item; if (l_Item.rOpenCount >= cTooMuchOpened) then theTooMuchIndex := l_Index; Exit; end;//l_Item.rFileName = aFileName Add(TReadFileInfo.Create(aFileName)); end;//for l_Index end; ... class constructor TReadFileInfoList.Create; begin inherited; f_ReadFileInfoList := TReadFileInfoList.Create; end; class destructor TReadFileInfoList.Destroy; begin FreeAndNil(f_ReadFileInfoList); inherited; end; ... class function TReadFile.Make(const aFileName: String): IStream; var l_TooMuchIndex : Integer; begin if (CurrentTreadID = MainThreadID) then begin f_ReadFileInfoList.IncOpenCount(aFileName, l_TooMuchIndex); if (l_TooMuchIndex >= 0) then SystemLog.ToLog('File "' + aFileName + '" too much opened'); end;//CurrentTreadID = MainThreadID Result := Create(aFileName); end; ... var l_F : IStream; begin l_F := TReadFile.Make('aFileName'); end;
-- что мы тут сделали?
А тут мы научились "понимать", что какие-то файлы "слишком много открываются".
Что будем делать дальше?
Попробуем "закешировать" эти файлы. Т.е. "объекты их реализующие".
Ну как-то так:
type TReadFileInfo = record public rFileName : String; rOpenCount : String; rStream: IStream; constructor Create(const aFileName: String); end;//TReadFileInfo TReadFileInfoList = class(TList<TReadFileInfo>) protected pocedure IncOpenCount(const aFileName: String; out theTooMuchIndex: Integer); end;//TReadFileInfoList TReadFile = class(TInterfacedObject, IStream) private class var f_ReadFileInfoList : TReadFileInfoList; protected class constructor Create; class destructor Destroy; public constructor Create(const aFileName: String); class function Make(const aFileName: String): IStream; end;//TReadFile ... constructor TReadFileInfo.Create(const aFileName: String); begin inherited; rFileName := aFileName; rOpenCount := 0; rStream := nil; end; ... const cTooMuchOpened = 1000; procedure TReadFileInfoList.IncOpenCount(const aFileName: String; out theTooMuchIndex: Integer); var l_Index : Integer; l_Item : TReadFileInfo; begin theTooMuchIndex := -1; for l_Index := 0 to Pred(Count) do begin l_Item := Items[l_Index]; if (l_Item.rFileName = aFileName) then begin Inc(l_Item.rOpenCount); Items[l_Index] := l_Item; if (l_Item.rOpenCount >= cTooMuchOpened) then theTooMuchIndex := l_Index; Exit; end;//l_Item.rFileName = aFileName Add(TReadFileInfo.Create(aFileName)); end;//for l_Index end; ... class constructor TReadFileInfoList.Create; begin inherited; f_ReadFileInfoList := TReadFileInfoList.Create; end; class destructor TReadFileInfoList.Destroy; begin FreeAndNil(f_ReadFileInfoList); inherited; end; ... class function TReadFile.Make(const aFileName: String): IStream; var l_TooMuchIndex : Integer; begin if (CurrentTreadID = MainThreadID) then begin f_ReadFileInfoList.IncOpenCount(aFileName, l_TooMuchIndex); if (l_TooMuchIndex >= 0) then begin SystemLog.ToLog('File "' + aFileName + '" too much opened'); l_Item := f_ReadFileInfoList.Items[l_TooMuchIndex]; if (l_Item.rStream <> nil) then begin Result := l_Item.rStream; // - возвращаем ранее закешированный поток Result.Seek(0, STREAM_SET); // - перематываем поток на начало (это - ВАЖНО), не зря я говорил (выше), про МНОГОПОТОЧНОСТЬ Exit; // - выходим, т.к. уже нашли нужный файл end;//l_Item.rStream <> nil end;//l_TooMuchIndex >= 0 end;//CurrentTreadID = MainThreadID Result := Create(aFileName); if (l_TooMuchIndex >= 0) then begin l_Item := f_ReadFileInfoList.Items[l_TooMuchIndex]; Assert(l_Item.rStream = nil, 'Убеждаемся в очевидном'); l_Item.rStream := Result; // - запоминаем НАШ объект f_ReadFileInfoList.Items[l_TooMuchIndex] := l_Item; // - сохраняем его в КЕШЕ end;//l_TooMuchIndex >= 0 end; ... var l_F : IStream; begin l_F := TReadFile.Make('aFileName'); end;
-- что мы тут сделали?
А мы тут устроили кеш файлов. Т.е. не открываем их повторно, а получаем из кеша.
Повторю - пример надуманый, но "почти из жизни".
Какие есть проблемы?
А например такие, что "просто так DeleteFile уже сложно позвать".
Почему?
А потому, что он "может быть открыт".
Ну и "смежные проблемы".
Которые можно решить примерно так:
Звать не:
DeleteFile('aFileName');
а:
... class procedure TReadFile.DeleteFile(const aFileName: String); begin f_ReadFileInfoList.Clear; // - отпустили ВСЕ файлы, хотя можно и ВЫБОРОЧНО отпускать, но не хочется загромождать код SysUtils.DeleteFile(aFileName); end; ... TReadFile.DeleteFile('aFileName');
-- но это уже - "тонкости", хотя и небезпроблемные.
Ну вот собственно и всё, что я хотел ещё сказать о фабриках.
Понимаю - "слом шаблона".
Но может быть - кому-нибудь понравится.
P.S. Может быть какие-то запятые или пробелы стоя не так как хотелось бы. Но я ЧЕСТНО СТАРАЛСЯ. Хотя и писал "с листа". И код - не компилировал. Хотел лишь идею проиллюстрировать.
P.P.S. Если кто-то захочет спросить про SHARE_READ или SHARE_WRITE - скажу - "это правильный ход мыслей", но тут же процитирую одного своего педагога, который на вопрос "почему мы берём тангенс бесконечно малого, а не само бесконечно малое" ответил - "давайте на глупые вопросы Саши Люлина не будем тратить время".
Думаю с нашими Application.ProcessMessages вот это вот
ОтветитьУдалитьif (CurrentTreadID = MainThreadID) then
begin
все равно не спасет. Я бы постремался так кэшировать..
Ром, там же выше написано "потокобезопасность оставляем за скобками" :-)
УдалитьА по поводу - "нашего", здесь же чисто "идея". А вот "реализацию" - увидишь в CVS ;-) Она вовсю уже молотит :-)
Ну и там конечно честная потокобезопасность. Без дураков.
Ключевое слово скажи где искать. Моей фантазии не хватает чтоб придумать, как можно защититься от повторного получения стрима через processMessages в одном(!) (главном) потоке, с катастрофической поломкой Position.
ОтветитьУдалить