По мотивам:
Коротко. Про 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.
ОтветитьУдалить