В C++ делают так:
namespace foo {
void bar() { }
}
using namespace foo; // используют так
bar();
foo::bar(); // или так
using foo::bar; // или так
bar();
В Common LISP нет пространств имён, но есть понятие пакета, которому принадлежат символы:(defpackage :foo) ; определяем пакет
(in-package :foo) ; делаем foo текущим пакетом, теперь доступны имена из пакета :foo
(defun bar () ()) ; bar теперь определён в пакете foo
; в один момент времени текущим является один пакет, используя in-package можно
; переключать текущий пакет. bar теперь доступен либо после переключания текущего
; пакета используя in-package, либо bar в defpackage должен быть включён в список
; экспорта пакета foo:
(defpackage :foo
(:export :bar))
; тогда из другого пакета bar доступен так:
(foo:bar)
; наследование символов пакетом из другого пакета осуществляется так:
(defpackage :foo
(:use :cl)) ; импортируем символы из "COMMON-LISP"
; если надо импортировать только определённые имена, то используют import-from:
(defpackage :foo
(:import-from :buzz :jazz))
; если же надо импортировать всё кроме некоторых имён, то используется shadow:
(defpackage :foo
(:use :buzz)
(:shadow :bar)) ; bar из :foo не будет перекрываться одноимёнными символами из
; импортируемых пакетов
; и, наконец, :shadowing-import-from скрывает указанные имена из указанных пакетов.
; Требуется для разрешения неоднозначности при иморте нескольких пакетов содержащих
; символы с одинаковыми именами:
(defpackage :foo
(:use :buzz :jazz)
(:shadowing-import-from :jazz :bar)) ; jazz:bar будет скрыт
Аналога вложенных пространств имён в CL нет, также нет аналога создания псевдонима для пространства имён из C#:using Co = Company.Proj.Nested;Подробнее в PCL: 21. Программирование по-взрослому: Пакеты и Символы.
Более интересная тема - ООП, реализуемое CLOS.
; создаём класс:
(defclass foo ()
())
; наследуемся:
(defclass bar (foo)
())
; от нескольких:
(defclass bar (foo jazz)
())
; поля в классе:
(defclass foo ()
((field-a) ; просто поле, спецификаторов доступа (private, public, e.t.c.) в CLOS нет
(field-b :initform 0) ; значение по-умолчанию для поля
(field-c :initarg :field-c) ; имя параметра, значение которого при создании объекта
; проинициализирует поле
(field-d :initarg :field-d :initform (error "Must supply field-d."))
; строка выше потребует обязательного использования именованного параметра для
; инициалмизации поля.
(field-e :initform 0 :initarg :field-e))) ; всё вместе
; создаём объект класса:
(defparameter *b*
(make-instance 'bar))
; для последнего примера, класса foo:
(defparameter *f*
(make-instance 'foo :field-c 5 :field-d "ABC"))
; если требуется инициализация слотов (полей) зависящая от значения других полей, то
; требуется конструктор:
(defmethod initialize-instance :after ((self foo) &key)
(setf (slot-value self 'field-a) 10) ; slot-value позволяет получить значение поля объекта
(setf (slot-value self 'field-b) (+ (slot-value self 'field-a) 15))
(setf (slot-value self 'field-c) 30))
; спецификатор :after приводит к тому, что метод срабатывает после основной инициализации,
; соответственно, данный конструктор перекрывает устанавливыемые значения полей с помощью
; :initarg и :initform используемые в make-instance
; основная форма создания методов для класса:
(defmethod methodname ((variablename1 classname1) (variablename2 classname2))
())
; самое шокирующее открытие, это то, что методы не принадлежат классам, однако, в отличии от
; C++/C# "внешние" методы обеспечивают динамическую диспетчерезацию в зависимости от типа
; переданного объекта, при этом классы не обязаны быть связанными иерархией наследования:
(defclass class1 () ())
(defclass class2 () ())
; выглядит как статическая перегрузка метода, на самом деле, здесь динамическая диспетчеризация
(defmethod foo ((o class1))
"class 1")
(defmethod foo ((o class2))
"class 2")
(defparameter *src* ; список объектов
(mapcar #'make-instance '(class1 class2)))
(defparameter *rslt* ; результат работы реализаций обобщённого метода для каждого
(mapcar #'foo *src*)) ; элемента списка объектов(сам обобщённый метод остался за кадром,
; на самом деле, использование defmethod при отсутствующем defgeneric
(print *rslt*) ; приводит к созданию defgeneric определяющего обобщённый метод)
==> ("class 1" "class 2")
А теперь то, что делает CL/CLOS уникальным - мультиметоды. Мультиметоды - это просто. Если метод специализирует более одного параметра обобщённой функции - то это мультиметод. Практически тоже самое, что и выше, только добавить параметров других типов. И также как и выше это будет не перегрузка функций, как в статически типизированных языках, а диспетчеризация в рантайме в зависимости от переданных объектов.В каких случаях оно требуется. В любых, когда нужно сделать диспетчеризацию зависящую от типов нескольких объектов. Широко распространён пример с обходом древовидной структуры, где для каждого типа узла вызываются определённые операции. Чтобы избежать в коде уродливых switch/case в языках с передачей сообщений (C++/Java/SmallTalk/C#/e.t.c.) используют паттерн "Visitor", в реализации которого используется двойная диспетчеризация. Объяснять здесь "Visitor" не буду, дам просто пример кода на C++. Есть ряд объектов оконной системы: Window, Panel - для которых определён ряд событий: MouseEvent, KeyEvent. Использование "Visitor" для вызова обработчиков событий:
class Panel;
class Window;
class Event {
public:
virtual void Process(Panel&) = 0;
virtual void Process(Window&) = 0;
};
class View {
public:
virtual void accept(Event*) = 0;
};
class Panel : public View {
public:
void accept(Event* e) { e->Process(*this); }
};
class Window : public View {
public:
void accept(Event* e) { e->Process(*this); }
};
class MouseEvent : public Event {
public:
void Process(Panel&) { cout << "MouseEvent.Panel" << endl; }
void Process(Window&) { cout << "MouseEvent.Window" << endl; }
};
class KeyEvent : public Event {
public:
void Process(Panel&) { cout << "KeyEvent.Panel" << endl; }
void Process(Window&) { cout << "KeyEvent.Window" << endl; }
};
int main(int, char**)
{
Panel* panel = new Panel;
Window* window = new Window;
KeyEvent* ke = new KeyEvent;
MouseEvent* me = new MouseEvent;
panel->accept(ke);
window->accept(me);
...
В CLOS реализация подобных вещей значительно проще. Поскольку, методы не принадлежат класам, необходимости городить систему классов для того, чтобы обеспечить диспетчеризацию в зависимости от типов нескольких объектов нет:(defclass class1 () ()) (defclass class2 () ()) (defclass class3 () ()) (defmethod multi ((o1 class1) (o2 class2) (o3 class3)) "m1 1 2 3") (defmethod multi ((o1 class1) (o2 class1) (o3 class1)) "m2 1 1 1") (multi (make-instance 'class1) (make-instance 'class1) (make-instance 'class1)) ==> "m2 1 1 1" (multi (make-instance 'class1) (make-instance 'class2) (make-instance 'class3)) ==> "m1 1 2 3"А теперь о сахаре. Для C# в 2005 году изобрели автоматические свойства:
public string Name { get; private set; }
В CLOS оно появилось намного раньше: (defclass foo ()
((field1
:initform 7
:reader field1 ; создаём обобщённые функции для чтения
:writer (setf field1)) ; и записи поля
(field2
:initform 5
:accessor field2))) ; а тут сразу одним махом
(defparameter *f*
(make-instance 'foo))
(field1 *f*)
==> 7
(setf (field1 *f) 12)
(field1 *f*)
==> 12
Вот, вроде бы и всё про классы. Подробности тут: 17. Переходим к объектам: Классы.
Комментариев нет:
Отправить комментарий