2013-08-12 3 views
3

Я работаю над шаблоном класса общей коллекции, допустим, List(T), где я хотел бы сделать что-то вроде позднего статического привязки php. Лучше всего проиллюстрировать некоторым упрощенным образцом кода. Этот код компилируется отлично, как на dmd, но для его изменения требуется небольшое изменение.Последнее статическое связывание в d

module main; 

import std.stdio; 
import std.string; 

class List(T) 
{ 
    private T[] _list; 

    public void append(T t) 
    { 
     _list ~= t; 
    } 

    // this is where some help is needed... 
    public List select(bool delegate(T t) dg) 
    { 
      // auto should be whatever subclass of List(T) is calling this method. 
     auto result = new List!T(); 
     foreach(t; _list) 
     { 
      if (dg(t)) result.append(t); 
     } 
     return result; 
    } 

    int opApply(int delegate(ref T) dg) 
    { 
     int result = 0; 

     for (int i = 0; i < _list.length; i++) 
     { 
      result = dg(_list[i]); 
      if (result) 
       break; 
     } 
     return result; 
    } 
} 

enum Gender 
{ 
    MALE, 
    FEMALE, 
    SECRET 
} 

class Person 
{ 
    private string _firstName; 
    private string _lastName; 
    private string _email; 
    private Gender _gender; 

    @property public string firstName() {return _firstName;} 
    @property public string lastName() {return _lastName;} 
    @property public string email() {return _email;} 
    @property public Gender gender() {return _gender;} 


    public this() 
    { 
    } 

    public this(string firstName, string lastName, Gender gender = Gender.SECRET, string email = "[email protected]") 
    { 
     this(); 

     this._firstName = firstName; 
     this._lastName = lastName; 
     this._gender = gender; 
     this._email = email; 
    } 

    override public string toString() 
    { 
     if (email.length > 0) 
     { 
      return "%s %s <%s>".format(firstName, lastName, email); 
     } 
     else 
     { 
      return "%s %s".format(firstName, lastName); 
     } 
    } 
} 

class PeopleList : List!Person 
{ 
    // I would like to be able to make this: public PeopleList selectByGender(Gender gender) 
    public List!Person selectByGender(Gender gender) 
    { 
     return select(p => p.gender == gender); 
    } 
} 

void main(string[] args) 
{ 
    auto people = new PeopleList(); 
    people.append(new Person("Kris", "Herlaar", Gender.MALE)); 
    people.append(new Person("John", "Doe", Gender.MALE)); 
    people.append(new Person("Steve", "Wozniak", Gender.MALE)); 
    people.append(new Person("Walter", "Bright", Gender.MALE)); 
    people.append(new Person("Amelia", "Earhart", Gender.FEMALE, null)); 
    people.append(new Person("Susan", "Anthony", Gender.FEMALE, null)); 

    foreach(p; people.selectByGender(Gender.FEMALE)) 
    { 
     writeln(p); 
    } 
} 

Как будет идти о том, чтобы убедиться, что PeopleList.select также возвращает экземпляр PeopleList вместо List!Person, так что закомментирована декларация selectByGender является правильным?

Я мог бы, наверное, издеваться над Object.factory(this.classinfo.name) внутри реализации и фактически получить правильный тип экземпляра для result, но я полагаю, что это не помогло бы с объявленным обратным типом.

Я хотел бы использовать методы цепочки, поэтому мне понадобится компилятор, чтобы позволить мне возвращать экземпляры любого подкласса, вызывающего List(T).select Я предполагаю, что это можно сделать с помощью вложенного шаблона, но не удалось придумать что-нибудь, что бы скомпилировалось, не говоря уже о том, чтобы выглядеть элегантно.

Дополнительная информация в ответ на полученное обратной

Я отдаю себе отчет в std.algorithm и filter, в реальной жизни; этот код не представляет собой фактический случай использования, а мысленный эксперимент, чтобы узнать больше о возможностях/пределах D и его «шаблонах».

+0

aka вы хотите что-то похожее на клон java; создайте новый экземпляр с текущим (runtime) типом –

+0

Я знаю, что это технически не «поздняя» статическая привязка, но это не клонирование объектов, за которыми я работаю. Я просто хочу вернуть тип подкласса из метода суперкласса, где суперкласс не знает о подтипе. – Kris

+1

Возможно, вам стоит просто пойти на подход C++ STL и создать силовую композицию вместо наследования для ваших контейнеров. Это решит вашу проблему. Или у вас есть веская причина для наследования наследуемых контейнеров? – RedX

ответ

5

Вы можете использовать Template This Parameters, как описано в http://dlang.org/template.html#TemplateThisParameter

Вот несколько примеров кода со страницы.

interface Addable(T) { 
    final R add(this R)(T t) { 
     return cast(R)this; // cast is necessary, but safe 
    } 
} 

class List(T) : Addable!T { 
    List remove(T t) { 
     return this; 
    } 
} 

void main() { 
    auto list = new List!int; 
    list.add(1).remove(1); // ok 
} 

И вот ваш код, используя его.

module main; 

import std.stdio; 
import std.string; 

class List(T) 
{ 
    private T[] _list; 

    public void append(T t) 
    { 
     _list ~= t; 
    } 

    // select is now templatized based on the most derived class 
    public This select(this This)(bool delegate(T t) dg) 
    { 
     // auto should be whatever subclass of List(T) is calling this method. 
     auto result = new This(); 
     foreach(t; _list) 
     { 
      if (dg(t)) result.append(t); 
     } 
     return result; 
    } 

    int opApply(int delegate(ref T) dg) 
    { 
     int result = 0; 

     for (int i = 0; i < _list.length; i++) 
     { 
      result = dg(_list[i]); 
      if (result) 
       break; 
     } 
     return result; 
    } 
} 

enum Gender 
{ 
    MALE, 
    FEMALE, 
    SECRET 
} 

class Person 
{ 
    private string _firstName; 
    private string _lastName; 
    private string _email; 
    private Gender _gender; 

    @property public string firstName() {return _firstName;} 
    @property public string lastName() {return _lastName;} 
    @property public string email() {return _email;} 
    @property public Gender gender() {return _gender;} 


    public this() 
    { 
    } 

    public this(string firstName, string lastName, Gender gender = Gender.SECRET, string email = "[email protected]") 
    { 
     this(); 

     this._firstName = firstName; 
     this._lastName = lastName; 
     this._gender = gender; 
     this._email = email; 
    } 

    override public string toString() 
    { 
     if (email.length > 0) 
     { 
      return "%s %s <%s>".format(firstName, lastName, email); 
     } 
     else 
     { 
      return "%s %s".format(firstName, lastName); 
     } 
    } 
} 

class PeopleList : List!Person 
{ 
    public PeopleList selectByGender(Gender gender) 
    { 
     return this.select(p => p.gender == gender); 
    } 
} 

void main(string[] args) 
{ 
    auto people = new PeopleList(); 
    people.append(new Person("Kris", "Herlaar", Gender.MALE)); 
    people.append(new Person("John", "Doe", Gender.MALE)); 
    people.append(new Person("Steve", "Wozniak", Gender.MALE)); 
    people.append(new Person("Walter", "Bright", Gender.MALE)); 
    people.append(new Person("Amelia", "Earhart", Gender.FEMALE, null)); 
    people.append(new Person("Susan", "Anthony", Gender.FEMALE, null)); 

    foreach(p; people.selectByGender(Gender.FEMALE)) 
    { 
     writeln(p); 
    } 
} 
+0

Теперь это потрясающе! – Kris

6

Это несчастливое использование наследования. PersonList не должен существовать: он никоим образом не является полиморфным.

Я думаю, что вы намерены сделать это, чтобы предоставить вспомогательный метод: выбор людей по полу из списка. D имеет что-то, называемое унифицированным синтаксисом вызова функций, которое позволяет вам вызывать бесплатные функции, как если бы первым параметром был фактический экземпляр this. Таким образом, вы могли бы переписать код так:

public List!People selectByGender(List!People list, Gender gender) 
{ 
    return list.select(p => p.gender == gender); 
} 

void main(string[] args) 
{ 
    auto people = new List!People(); 

    // ... 

    foreach(p; people.selectByGender(Gender.FEMALE)) 
    { 
     writeln(p); 
    } 
} 

Я не знаю, если вы смотрели в std.algorithm еще. Но это в основном делает излишним код вашего List(T). Вы можете просто создать массив (или любой другой range из Person) с вашими людьми, а затем сделать:

foreach (p; people.filter!(p => p.gender == Gender.FEMALE)) 
{ 
    writeln(p); 
} 

и сделать с ней. Этот стиль напоминает (существенные элементы) функционального программирования, труб и фильтров или компонентного программирования (внутри сообщества D), как бы вы его ни называли. Также filter и др. Не будут выделять новый список, а скорее генерировать их результаты из исходного массива «на лету» или лениво или поточно. Вы можете заставить их сохранить результаты в новом буфере, используя array из std.array.

Это один из основных случаев, когда заставлять себя думать о Наследовании иератии - не самый элегантный способ.

+1

Я пытаюсь найти ограничения на то, что возможно с d, а не делать что-либо в частности. Просто учите себя здесь. – Kris

Смежные вопросы