Интерфейсы (interfaces)
Интерфейсы — это мощный механизм, позволяющий абстрактно описать объект и легко подменять конкретные реализации.
Irnix считает интерфейсом любой объект, имя которого начинается с __ и заканчивается символами __, например __interface_object__. Для таких объектов действует особое правило: директория такого объекта должна содержать только два файла: контракт в файле .self, а также ссылка на конкретную реализацию.
Например, давайте создадим некий __logger__, который имеет метод log, что принимает stdin, форматирует сообщение и всегда выводит его:
~/.local/share/irnix
└── __logger__
└── .self
Содержимое .self:
log: stdin! stdout!
Если попытаться сейчас вызвать метод, то Irnix выведет сообщение об ошибке, говорящее о том, что в директории интерфейса должно быть ровно два файла (.self и ссылка на конкретную реализацию).
Давайте создадим конкретную реализацию и назовем ее logm:
~/.local/share/irnix
├── __logger__
│ └── .self
└── logm
├── .self
├── log_level -> /bin/logm
└── log -> /bin/logm
А в .self добавим уже два метода:
log: stdin! stdout!
log_level: stdin! level! stdout!
log принимает stdin, форматирует его и всегда выводит что-то в stdout. А метод log_level работает также как и log, но также принимает один аргумент, а именно уровень.
Важно отметить, что в реализации logm все методы это ссылки на один и тот же бинарный файл logm, который просто форматирует сообщение и выводит его. Более подробно о том, как можно использовать ссылки в Irnix и добиваться высокой гибкости мы рассмотрим в главе "Работа с ссылками".
Теперь просто создадим жесткую ссылку на директорию logm в директории интерфейса:
~/.local/share/irnix
├── __logger__
│ ├── .self
│ └── logm -> ../logm
└── logm
├── .self
├── log_level -> /bin/logm
└── log -> /bin/logm
После чего метод можно спокойно вызывать как метод на обычном объекте:
echo message | irnix e __logger__.log
Во время запуска Irnix сравнивает оба контракта в __logger__ и в реализации logm для метода log, и если они совпадают, значит реализация подходит. Далее все как обычно, Irnix проверяет вызов и вызывает конкретную реализацию метода log.
Вы также можете не создавать ссылок, а создать реализацию объекта прямо в директории интерфейса:
~/.local/share/irnix
└── __logger__
├── .self
└── logm
├── .self
├── log_level -> /bin/logm
└── log -> /bin/logm
Но тогда вы теряете в гибкости, ведь реализацию будет уже не так просто заменить на другую. Ведь основной смысл использования таких интерфейсов заключается в том, что вы можете легко подменить реализацию, и при этом не меняя то, как команда будет вызвана.
Если при этом попытаться вызвать метод __logger__.log_level, то Irnix выведет следующее сообщение:
(Вызываемый метод "log_level" не указан в контракте интерфейса. Однако он указан в контракте объекта: "/home/illia/.local/share/irnix/logm")
The called method "log_level" is not specified in the interface contract. However, it is specified in the objects contract: "/home/illia/.local/share/irnix/logm"
Irnix понимает, что на самом деле такой метод существует, и он может его вызвать (и это было бы чем-то вроде приведения типов), но этого не происходит и это сделано намерено. Такое поведение позволяет избежать багов, ведь если поменять реализацию на новую, ее методы могут не полностью совпадать со старой реализацией.