2010-07-30 2 views
3

Я пишу фреймворк для запроса Mediawiki API. У меня есть класс Page, который представляет статьи на wiki, и у меня также есть класс Category, который is-aPage с более конкретными методами (например, с возможностью подсчета количества членов в категории. также есть метод Page#category?, который определяет, будет ли экземпляр Page объекта на самом деле представитель страницы категории Mediawiki, с помощью запроса API для определения пространства имен статьи.Преобразование класса в подкласс при создании экземпляра

class Page 
    def initialize(title) 
    # do initialization stuff 
    end 

    def category? 
    # query the API to get the namespace of the page and then... 
    namespace == CATEGORY_NAMESPACE 
    end 
end 

class Category < Page 
    # ... 
end 

То, что я хотел бы сделать, это быть в состоянии определить, пытается ли пользователь моей инфраструктуры создавать экземпляр класса Mediawiki с помощью объекта страницы (то есть Page.new("Category:My Category")), и если да, то создайте экземпляр Category, а не объект Page, непосредственно из конструктора Page.

Мне кажется, что это должно быть возможно, потому что это напоминает однонаправленное наследование таблицы в Rails, но я не уверен, как заставить его работать.

+1

Метод 'category?' В 'Page' подразумевает, что класс предка знает о дочернем классе, который не похож на хорошую идею, OOP-мудрый. Кроме того, вы можете немного уточнить свое последнее предложение? –

+0

@Mladen: Не уверен в синтаксисе Ruby, но, похоже, он хочет 'Category foo = new Page()' вместо 'Page bar = new Category()'. –

+3

Тогда он должен, вероятно, взглянуть на http://stackoverflow.com/questions/746207/ruby-design-pattern-how-to-make-an-extensible-factory-class и, как правило, немного рассказать о фабричной схеме , –

ответ

6

Хорошо, несколько вещей:

Вы не можете конвертировать экземпляр класса A к экземпляру подкласса A «s B. По крайней мере, не автоматически. B может (и обычно имеет) содержать атрибуты, отсутствующие в A, он может иметь совершенно другой конструктор и т. Д. Таким образом, AFAIK, ни один язык OO не позволит вам «преобразовать» классы таким образом.

Даже в статических типизированных языках, при создании экземпляра B, а затем присвоить его переменной a типа A, это еще пример B, он не превращается в его предка класса вообще.

Рубин динамический язык с мощными возможностями отражения, так что вы всегда можете решить, какой класс экземпляр во время выполнения - проверить это:

puts "Which class to instantiate: " 
class_name = gets.chomp 
klass = Module.const_get class_name 
instance = klass.new 

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

Другое дело: как я упоминал в комментарии, метод category? просто ошибается, поскольку он нарушает принципы ООП. В Ruby, вы можете - и должны - метод использования is_a?, поэтому проверка будет выглядеть следующим образом:

if instance.is_a? Category 
    puts 'Yes, yes, it is a category!' 
else 
    puts "Nope, it's something else." 
end 

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

Редактировать: После повторного чтения обновленного вопроса мне кажется, что правильным способом для вас будет создание фабричного класса и возможность его обнаружения и создания экземпляров разных типов страниц.Таким образом, пользователь не будет звонить Page.new непосредственно, а скорее назвать что-то вроде

MediaWikiClient.get_page "Category:My Category" 

и get_page метода будет экземпляр соответствующего класса.

+0

Я попытался сделать это более ясным в своем редактировании, но 'Page # category?' Не проверяет, какой тип класса является объект, он использует информацию страницы, чтобы определить, в каком пространстве имен он попадает. Я не знаю, где еще я бы поместил это ... –

+0

В любом случае, я думаю, что я не был знаком с шаблоном Factory, но, похоже, это путь, спасибо. –

+0

Ах, извините, я не понимал, что вы говорили о пространствах имен WikiMedia, а не о пространствах Ruby! :) В любом случае, место для проверки пространства имен WM и создания соответствующего класса соответственно будет фабрикой. Конечно, вы _can_ поместите заводской метод в свой «Page» класс, снова взгляните на другой вопрос - Брайан Кэмпбелл дал отличный ответ и образцы кода там. –

1

Вы можете определить метод, который создает экземпляр класса и возвращает экземпляр. Это знают как Factory Pattern


class PageFactory 
    def create() // the pattern uses "create".. but "new" is more Ruby' style 
    if (namespace == CATEGORY_NAMESPACE) 
     return Category.new 
    else 
     return Page.new 
    end 
    end 
end 

class ClientClass 
    def do_something() 
    factory = PageFactory.new 
    my_page = factory.create() 
    my_page.do_someting() 
    end 
end 

+0

Это не сработает. Метод 'new' в' PageFactory' является методом экземпляра, и вы вызываете метод класса в 'doSomething'. Кроме того, не рекомендуется переопределять метод 'new', поскольку он используется для создания экземпляра класса на нем. BTW, соглашение Ruby должно использовать имена underscore_separated методов, а не camelCased. –

1

Почему не так? Возможность сделать это - достаточно хорошая причина для этого!

class Page 
    def self.new(title) 
    if self == Page and is_category?(title) 
     Category.new(title) 
    else 
     super 
    end 
    end 

    def self.is_category?(title) 
    # ... (query the API etc.) 
    end 

    def initialize(title) 
    # do initialization stuff 
    end 

    def category? 
    # query the API to get the namespace of the page and then... 
    namespace == CATEGORY_NAMESPACE 
    end 
end 

class Category < Page 
    # ... 
end