2010-11-19 10 views
5

Tengo un módulo C++ que necesita obtener información de otras clases, sin conocer esas clases. El enfoque obvio es usar interfaces.¿Tiene una interfaz con muchos métodos virtuales? ¿O tener muchas interfaces con solo 1 método virtual?

Déjeme darle un ejemplo. Supongamos que tengo una biblioteca que administra libros, y todos los libros tienen sus propias características y funcionalidades, y para permitir que la biblioteca obtenga una característica de un libro o ejecute una función, el libro necesita implementar una interfaz. De esta manera:

class Library 
    { 
    public: 
     void addBook(IBook &book); 
    }; 

class IBook 
    { 
    public: 
     string getAuthor() = 0; 
     string getTitle()  = 0; 
     string getISBNCode() = 0; 
     size_t getNofPages() = 0; 
     size_t getNofImages() = 0; 
     double getPrice()  = 0; 
     void printBook() = 0; 
     void convertToPdf() = 0; 
    }; 

Desafortunadamente, no tiene sentido implementar todos estos métodos para todo tipo de libros.

  • Algunos libros no tienen imágenes (por lo que no quiero poner en práctica getNofImages())
  • Algunos libros no tienen un código ISBN
  • Algunos libros no se pueden comprar, por lo no tienen un precio
  • Algunos libros no se pueden imprimir
  • Algunos libros no se pueden convertir a PDF

porque sólo tengo 1 interfaz, estoy obligado a poner en práctica todo lo necesario para todos los libros devuelven 0, devuelven "" o no hacen nada en la implementación si es irrelevante.

Una alternativa podría ser la de dividir estas interfaces en muchas interfaces, así:

class IBook 
    { 
    public: 
     string getAuthor() = 0; 
     string getTitle()  = 0; 
     size_t getNofPages() = 0; 
    }; 

class IISBNGetter 
    { 
    public: 
     string getISBNCode() = 0; 
    }; 

class IImagesGetter 
    { 
    public: 
     size_t getNofImages() = 0; 
    }; 

class IBuyable 
    { 
    public: 
     double getPrice()  = 0; 
    }; 

class IPrintable 
    { 
    public: 
     void printBook() = 0; 
    }; 

class IConvertible 
    { 
    public: 
     void convertToPdf() = 0; 
    }; 

clases libro, entonces sólo tienen que poner en práctica las interfaces que realmente quieren apoyar.

Adición de un libro a la biblioteca a continuación, se convierte en algo parecido a esto:

bookid = myLibrary->addBook (myBook); 
myLibrary->setISBNGetter (bookid, myBook); 
myLibrary->setImageGetter (bookid, myBook); 
myLibrary->setBuyable  (bookid, myBook); 

La ventaja de tener diferentes interfaces es que está claro para la biblioteca que apoya lo que, y nunca tiene el riesgo de llamar a algo eso simplemente no es compatible.

Sin embargo, dado que cada libro puede tener cualquier combinación posible de características/funcionalidades, termino con muchas interfaces con solo 1 método.

¿No hay una mejor manera de organizar las interfaces para obtener algo como esto?

También estaba pensando en usar expresiones Lambda pero detrás de las pantallas esto es casi lo mismo que tener muchas interfaces con solo 1 método.

¿Alguna idea?

Respuesta

8

que tendría IBook para implementar todos los métodos:

class IBook 
    { 
    public: 
     virtual ~IBook() {} 

     virtual string getAuthor() { return ""; } // or some other meaningful default value 
     virtual string getTitle() { return ""; } 
     virtual string getISBNCode() { return ""; } 
     virtual size_t getNofPages() { return 0; } 
     virtual size_t getNofImages() { return 0; } 
     virtual double getPrice() { return .0; } 
     virtual void printBook() {} 
     virtual void convertToPdf() {} 
    }; 

ya que su alternativa es demasiado complicado para mí, que iba a terminar con una gran cantidad de interfaces de confusas. De todos modos, podría verificar si el Visitor pattern podría aplicarse aquí.

2

Una solución podría ser la de mantener su interfaz de base con métodos virtuales puros, pero tienen sus implementaciones reales heredan de una clase intermedia que proporciona implementaciones por defecto para los métodos virtuales.

Una implementación común de esa clase intermedia sería arrojar algún tipo de excepción "MethodNotImplemented" en cada método, por lo que el usuario de la clase puede detectarlas caso por caso.

Como las excepciones son un poco costosas para un caso en el que llamar a métodos no existentes no sería "excepcional", también existe el enfoque de tener esos métodos vacíos o devolver valores predeterminados, como publicó Simone.

2

supongo que no es necesario para llegar a cualquier extremo, pero para elegir camino del medio. Tener una interfaz no es buena, rompe Interface Segregation Principle (ISP) por otra parte, tener tantas interfaces estropeará tu código también. Dejaría un IBook central y pensaría en el resto. Por ejemplo, IPrintable e IConvertible (para pdf convert) pueden ir en una interfaz. Probablemente IBuyable e IISBNGetter se aliarán.

1

Otra solución sería la de mantener la interfaz, sino para impulsar el uso :: opcional para los valores de retorno que puede estar vacío.

class IBook 
    { 
    public: 
     virtual ~Ibook(){} 

     virtual string getAuthor() = 0; 
     virtual string getTitle()  = 0; 
     virtual string getISBNCode() = 0; 
     virtual size_t getNofPages() = 0; 
     virtual size_t getNofImages() = 0; 
     virtual boost::optional<double> getPrice()  = 0; // some have no price 
     virtual void printBook() = 0; 
     virtual void convertToPdf() = 0; 
    }; 
2

creo que usted debe hacer una distinción entre realidad tener un ISBN, e implementar una interfaz que consulta ISBN (en este caso, IBook).

No hay razón para que no pueda decir, como parte de la definición de "libro", que un libro es algo de lo que "es posible encontrar si tiene un ISBN, y si es así".

Si no te gusta la devolución de valores vacíos para indicar "ninguna", eso es justo lo suficiente. En algunos dominios, una cadena vacía es un valor válido, por lo que ni siquiera sería posible. Usted podría tener:

bool hasISBNcode(); 
string getISBNcode(); 

o:

std::pair<bool, string> getISBNcode(); 

o similar.

De lo contrario, se encontrará con dynamic_cast en todas partes antes de llamar a cualquiera de las funciones de IBook, y también se encontrará con 2^n clases concretas diferentes para diferentes tipos de libros. O, en su código de ejemplo, involucra a la biblioteca en el negocio de si un libro tiene un ISBN (lo cual me parece incorrecto; no tiene nada que ver con la biblioteca, es solo una propiedad del libro). Ninguno de ellos es particularmente divertido para trabajar, y no parecen necesarios aquí.

Si ese tipo de cosas se veían necesario, tal vez podría utilizar estrategias. Defina ConcreteBook como capaz de busque para un ISBN que utiliza algún objeto auxiliar, sin que la clase de libro sepa cómo se realiza la búsqueda. Luego, inserte diferentes objetos para hacer eso buscando, dependiendo de si un libro en particular tiene uno o no. Parece un poco exagerado, sin embargo, para lo que probablemente sea solo una columna de nulos en una base de datos en alguna parte.

+0

Totalmente de acuerdo, los libros son, naturalmente, considerados potencialmente tener ISBN y precio. Dado un libro en particular, me gustaría preguntar si se puede comprar y cuál es el precio, si tiene un ISBN y cuál es ese valor de ISBN ... esas operaciones semánticamente pertenecen a la interfaz 'IBook'. Desde un punto de vista diferente, tener una interfaz 'ISBNGetter' parece implicar que diferentes objetos pueden tener ISBN, pero solo los libros lo tienen. No hay ninguna circunstancia bajo la cual usted use las interfaces adicionales en un objeto que no es también un 'IBook'. –

+0

@dribeas: sí, aunque tener 'IISBNGetter' derivado de' IBook' podría ser más problemático de lo que vale, incluso si fuera con interfaces adicionales. Creo que ISBN-13 es compatible con UPC, sin embargo. Si es así, entonces 'IUPCGetter' sería más general, e 'IBuyable' ciertamente lo es. 'IBuyable' podría ser una interfaz que valga la pena tener, independientemente de si está definido que todos los libros lo implementan, o solo algunos. –

1

Usted podría tener para cada libro un recipiente de punteros a basar la interfaz de objetos únicos, algo así como std::map<std::string, IBase>. Entonces requeriría la interfaz por nombre, obtendría el puntero (o nulo) y simplemente invocaría doDefault() en ella (o cambiaría el puntero a IDerived, si es necesario).Cada función de interfaz debería tener un puntero a Libro como su primer (o incluso único) parámetro: doDefault(const Book*).

1

Hay dos sólo un poco (muy ligeramente!) Relacionados con problemas:

  • abstracción apropiado de la jerarquía partes lógico.
  • La posibilidad de valores nulos.

Otros han proporcionado soluciones para cada problema. A saber, con respecto a la primera, no busques una interfaz de todo, no busques un método único por interfaz, pero intenta modelar la jerarquía que está allí. Y con respecto a este último, boost::optional, posiblemente aumentado con métodos de consulta separados para la existencia del elemento de datos.

Solo quiero enfatizar, que puede no ser aparente por las respuestas presentes mientras escribo esto, que realmente son dos problemas separados.

En cuanto al estilo (otro aspecto de la claridad), ¿qué es todo esto getSin material de Javaism?

x = 2*getSin(v)/computeCos(v)

no tiene sentido en C++, acaba de escribir sin. :-)

Saludos & HTH.,

Cuestiones relacionadas