sirve - herencia multiple c++ ejemplo
Funciones miembro para información derivada en una clase (6)
Creo que está bien tener getSelectedDocs como función miembro. Es una operación perfectamente razonable para un DocContainer, por lo que tiene sentido como miembro. Las funciones de los miembros deberían estar ahí para hacer que la clase sea útil. No necesitan satisfacer algún tipo de requisito de minimización.
Una desventaja de trasladarlo fuera de la clase es que la gente tendrá que mirar en dos lugares cuando intente encontrar la manera de usar un DocContainer: deben buscar en la clase y también en el espacio de nombres de la utilidad.
Al diseñar una interfaz para una clase, normalmente me atrapa la idea de si debería proporcionar funciones miembro que puedan calcularse / derivarse utilizando combinaciones de otras funciones miembro. Por ejemplo:
class DocContainer
{
public:
Doc* getDoc(int index) const;
bool isDocSelected(Doc*) const;
int getDocCount() const;
//Should this method be here???
//This method returns the selected documents in the contrainer (in selectedDocs_out)
void getSelectedDocs(std::vector<Doc*>& selectedDocs_out) const;
};
¿Debo proporcionar esto como una función miembro de la clase o, probablemente, un espacio de nombres donde puedo definir este método? ¿Cuál es el preferido?
Creo que esto es perfectamente válido si el método:
- encaja en las responsabilidades de la clase
- no es demasiado específico para una pequeña parte de los clientes de la clase (al menos un 20%)
Esto es especialmente cierto si el método contiene lógica / cálculo complejo que sería más costoso de mantener en muchos lugares que solo en la clase.
El STL básicamente se ha destinado a interfaces pequeñas, por lo que en su caso, si y solo si getSelectedDocs
se puede implementar de manera más eficiente que una combinación de isDocSelected
y getDoc
, se implementaría como una función miembro.
Esta técnica puede no ser aplicable en cualquier lugar, pero es una buena regla de oro para evitar el desorden en las interfaces.
En general, probablemente deberías preferir funciones gratuitas. Piénselo desde una perspectiva de OOP.
Si la función no necesita acceso a ningún miembro privado, ¿por qué debería tener acceso a ellos? Eso no es bueno para la encapsulación. Significa más código que puede fallar potencialmente cuando se modifican las partes internas de la clase.
También limita la posible cantidad de reutilización de código.
Si escribiste la función como algo como esto:
template <typename T>
bool getSelectedDocs(T& container, std::vector<Doc*>&);
Entonces la misma implementación de getSelectedDocs funcionará para cualquier clase que exponga las funciones requeridas, no solo su DocContainer.
Por supuesto, si no le gustan las plantillas, se podría usar una interfaz, y aún así funcionaría para cualquier clase que implementara esta interfaz.
Por otro lado, si se trata de una función miembro, solo funcionará para esta clase en particular (y posiblemente para las clases derivadas).
La biblioteca estándar de C ++ sigue el mismo enfoque. Considere std::find
, por ejemplo, que se convierte en una función gratuita por esta razón precisa. No necesita conocer los aspectos internos de la clase en la que realiza la búsqueda. Solo necesita una implementación que cumpla con sus requisitos. Lo que significa que la misma implementación de find()
puede funcionar en cualquier contenedor, en la biblioteca estándar o en cualquier otro lugar.
Scott Meyers defiende lo mismo .
Si no le gusta llenar su espacio de nombres principal, puede, por supuesto, ponerlo en un espacio de nombres separado con funcionalidad para esta clase en particular.
La respuesta es probablemente "depende" ...
Si la clase es parte de una interfaz pública para una biblioteca que será utilizada por muchas personas que llaman, entonces hay un buen argumento para proporcionar una gran cantidad de funcionalidades para que sea fácil de usar, incluyendo algunas duplicaciones y / o cruces. Sin embargo, si la clase solo está siendo utilizada por una única persona que realiza la búsqueda en sentido ascendente, probablemente no tenga sentido proporcionar múltiples formas de lograr lo mismo. Recuerde que todo el código en la interfaz debe ser probado y documentado, por lo que siempre hay un costo para agregar ese último bit de funcionalidad.
Estoy de acuerdo con las respuestas de Konrad y jalf . A menos que haya un beneficio significativo de tener "getSelectedDocs", entonces satura la interfaz de DocContainer.
Agregar este miembro activa mi sensor de código maloliente . DocContainer es obviamente un contenedor, ¿por qué no utilizar iteradores para escanear documentos individuales?
class DocContainer
{
public:
iterator begin ();
iterator end ();
// ...
bool isDocSelected (Doc *) const;
};
Luego, use un functor que crea el vector de documentos como lo necesita para:
typedef std::vector <Doc*> DocVector;
class IsDocSelected {
public:
IsDocSelected (DocContainer const & docs, DocVector & results)
: docs (docs)
, results (results)
{}
void operator()(Doc & doc) const
{
if (docs.isDocSelected (&doc))
{
results.push_back (&doc);
}
}
private:
DocContainer const & docs;
DocVector & results;
};
void foo (DocContainer & docs)
{
DocVector results;
std :: for_each (docs.begin ()
, docs.end ()
, IsDocSelected (docs, results));
}
Esto es un poco más detallado (al menos hasta que tengamos lambdas ), pero una ventaja de este tipo de enfoque es que el tipo específico de filtrado no está acoplado con la clase DocContainer. En el futuro, si necesita una nueva lista de documentos que son "No seleccionados", no es necesario cambiar la interfaz a DocContainer, solo debe escribir una nueva clase "IsDocNotSelected".