В 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. Переходим к объектам: Классы.
Комментариев нет:
Отправить комментарий