2010-04-06 7 views
15

Aquí está mi código:objeto de diseño orientado sugerencia

class Soldier { 
public: 
    Soldier(const string &name, const Gun &gun); 
    string getName(); 
private: 
    Gun gun; 
    string name; 
}; 

class Gun { 
public: 
    void fire(); 
    void load(int bullets); 
    int getBullets(); 
private: 
    int bullets; 
} 

Tengo que llamar a todas las funciones miembro de la pistola sobre un objeto soldado. Algo así como:

soldier.gun.fire(); 

o

soldier.getGun().load(15); 

Entonces, ¿cuál es un mejor diseño? Ocultar el objeto de arma como un miembro privado y acceder a él con la función getGun(). ¿O convertirlo en un miembro público? O puedo encapsular todas estas funciones que harían la implementación más difícil:

soldier.loadGun(15); // calls Gun.load() 
soldier.fire(); // calls Gun.fire() 

Entonces, ¿cuál crees que es la mejor?

+3

Combina las respuestas de Stephen y Frustrated. Tener un 'get_gun()' privado que funcione para que el soldado consiga el arma, como muestra Stephen, pero dile al * soldado * qué hacer, no el arma, como muestra Frustrado. – GManNickG

+0

@GMan - Gracias, estoy de acuerdo. Sin embargo, en lugar del enfoque de @ Frustrado, decirle al soldado qué hacer sería el enfoque de @Austin. p.ej. soldier.Attack() en lugar de "soldier.loadGun()". – Stephen

+0

@Stephen: Sí, puedes hacer que la encapsulación sea tan profunda como quieras. Mi pregunta fue solo sobre esta parte. Por supuesto, puede decir soldier.Attack() y dentro de esta función puede verificar si (! Gun.getBullets()) devuelve o cosas por el estilo. – pocoa

Respuesta

21

, diría que ir con su segunda opción:

soldier.loadGun(15); // calls Gun.load() 
soldier.fire(); // calls Gun.fire() 

Inicialmente más trabajo, pero como el el sistema se vuelve más complejo, puede encontrar que un soldado querrá hacer otras cosas antes y después de disparar su arma (tal vez verifique si tienen suficiente munición y luego grite "¡¡¡Muerde los coños !!" antes de disparar, y murmure "eso tiene que doler" después, y verifique si necesitan una recarga). También oculta a los usuarios de la clase Soldier los detalles innecesarios de cómo exactamente se dispara el arma.

+3

+1 para su descripción de la clase Soldier: P –

+1

Me encantan sus ejemplos :) – Dinah

+2

Además, no diga loadGun, digamos prepareWeapon. De esta forma, cuando tu soldado está en un tanque, no está manipulando su pistola cuando debería estar girando el cañón del tanque. – jmucchiello

1

No hay una regla de oro que aplica el 100% de las veces. Es realmente una decisión de acuerdo a sus necesidades.

Depende de la cantidad de funcionalidad que desea ocultar/denegar el arma del acceso a la Solider.

Si usted quiere tener únicamente acceso de sólo lectura a la pistola que podría devolver una referencia constante a su propio miembro.

Si desea exponer solamente cierta funcionalidad que podría hacer funciones de contenedor. Si no desea que el usuario intente cambiar la configuración de la pistola a través del Soldier, realice las funciones de envoltura.

En general, veo la pistola como su propio objeto y si no te importa exponer toda la funcionalidad de la pistola, y no me importa permitir que las cosas se cambien a través del objeto Soldier, solo hazlo público.

Es probable que no quieren una copia de la pistola por lo que si haces un método GetGun() asegúrese de que usted no está devolviendo una copia de la pistola.

Si desea mantener el código simple entonces tienen el soldado responsable de tratar con la pistola. ¿Tu otro código necesita trabajar con la pistola directamente? ¿O puede un soldado saber siempre cómo trabajar/recargar su propia arma?

+0

pistola es privada en el ejemplo anterior, tendría que hacerse público o un método de acceso escrito como getGun() – Fermin

+0

@Brian: necesito acceder a todos los miembros públicos del objeto Gun del Soldier. – pocoa

+0

@pocoa: A continuación, devuelva una referencia a través de GetGun() o simplemente haga pública la pistola. Eso es probablemente lo más fácil. Cuando desee realizar cambios en la interfaz de su Gun más adelante, no necesitará cambiar también la interfaz de su clase de soldado. –

1

Proporcionar un "getGun()" o simplemente "pistola()".

Imagínese que un día usted puede necesitar para hacer que el método más complejo:

Gun* getGun() { 
    if (!out_of_bullets_) { 
    return &gun_; 
    } else { 
    PullPieceFromAnkle(); 
    return &secret_gun_; 
    } 
} 

Además, es posible que desee proporcionar un descriptor de acceso const que la gente pueda usar una pistola const en un soldado const:

const Gun &getGun() const { return gun_; } 
7

La Ley de Demeter diría para encapsular las funciones.

http://en.wikipedia.org/wiki/Law_of_Demeter

De esta manera, si desea algún tipo de interacción entre el soldado y el arma, que tienen un espacio para insertar el código.

Editar: Encontrado el artículo correspondiente desde el enlace de Wikipedia: http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf El ejemplo repartidor es muy, muy similar al ejemplo soldado que publique.

+3

Y tenga en cuenta que para evitar caer en la "Ley del bajo punto cuenta" (http://haacked.com/archive/2009/07/14/law-of-demeter-dot-counting.aspx) –

+0

Gran enlace Martinho ! Perfectamente resume mis sentimientos sobre LOD. –

+0

En este caso, dado "Gun" es un parámetro constructor, no creo que esto se aplique. No rompe la encapsulación ya que la clase del cliente necesita crear una instancia del arma para empezar. Eso no quiere decir que un método Soldier :: Attack (Target * t) no sea un mejor enfoque. – Stephen

5

De hecho, depende mucho de cuánto control desee tener.

Para modelar el mundo real, es posible que desee encapsular por completo el objeto de la pistola, y solo tiene un método soldier.attack(). El método soldier.attack() vería si el soldado portaba una pistola, y cuál era el estado de la pistola, y dispararía o la recargaría si fuera necesario. O posiblemente tirar el arma al objetivo y huir, si no hay suficiente munición Estuvieron presentes en cualquiera de las operaciones ...

11

En primer lugar, estaría violando la Law of Demeter accediendo a la Gun desde fuera de la clase Soldier.

yo consideraría métodos como estos en su lugar:

soldier.ArmWeapon(...); 
soldier.Attack(...); 

De esta manera también se puede poner en práctica su puño, cuchillo, granadas, bate de béisbol, gato láser, etc.

+1

+1 para el gato láser – FrustratedWithFormsDesigner

2

Por lo general, mi decisión se basa en la naturaleza de la clase de contenedor (en este caso, Soldado). O bien es completamente un POD o no lo es. Si no es un POD, hago todos los miembros de datos privados y proporciono métodos de acceso. La clase es un POD solo si no tiene invariantes (es decir, no hay manera de que un actor externo pueda hacer que su estado sea incoherente al modificar sus miembros). Tu clase de soldado se parece más a un no POD para mí, así que iría a la opción de método de acceso. Si devuelve una referencia constante o una referencia regular es su propia decisión, basada en el comportamiento de fuego() y los otros métodos (si modifican el estado de la pistola o no).

Por cierto, Bjarne Stroustrup habla un poco acerca de este tema en su sitio: http://www.artima.com/intv/goldilocks3.html

Una nota al margen: Yo sé que no es precisamente lo que pidieron, pero les gustaría consejo a tener en cuenta también las muchas menciones hechas en otras respuestas a la ley de Demeter: exponer los métodos de acción (que actúan sobre la pistola) en lugar de todo el objeto de la pistola a través de un método getter. Como el soldado "tiene" el arma (está en su mano y aprieta el gatillo), me parece más natural que los otros actores "le pidan" al soldado que dispare. Sé que esto puede ser tedioso si la pistola tiene muchos métodos para actuar, pero tal vez también se podrían agrupar en más acciones de alto nivel que el soldado expone.

+0

No, Soldier no es solo una clase de contenedor. Gracias por el enlace. – pocoa

3

Si expone la pistola, que permiten cosas más allá de las funciones miembro de la pistola, lo que probablemente no es una buena idea:

soldier.gun = anotherGun; // where did you drop your old gun? 

Si utiliza getGun(), las llamadas mirar un poco feo, pero puedes agregar funciones a Gun sin modificar Soldier.

Si encapsulas las funciones (que recomiendo) puedes modificar la Pistola o introducir otras clases (derivadas) de Pistola sin cambiar la interfaz a Soldado.

0

Encapsula las funciones para proporcionar una interfaz de usuario coherente, incluso si luego cambias la lógica.Las convenciones de nomenclatura dependen de usted, pero normalmente no utilizo "getFoo()", sino simplemente "foo()" como accesadores y "setFoo()" como instaladores.

  • return reference-to-const cuando puede (Artículo C++ efectivo n. ° 3).
  • Prefiero consts, enumeraciones, y inlines a la utilización de números codificados duro (Tema 4 #)
  • proporcionan convenciones de nombres únicos para sus miembros privados para distinguirlos de los argumentos
  • Use valores sin signo en el que tienen sentido para mover los errores de compile time
  • Cuando los valores de const, como los máximos, se aplican a una clase completa. Hazlos estáticos.
  • Si va a heredar, asegúrese de que sus destructores son virtuales
  • inicializar todos los miembros de configuraciones normales

Así es como se ven las clases después de eso. CodePad

#include <iostream> 
#include <string> 
#include <stdint.h> 

using namespace std; 

class Gun 
{ 
public: 
    Gun() : _bullets(0) {} 
    virtual ~Gun() {} 
    void fire() {cout << "bang bang" << endl; _bullets--;} 
    void load(const uint16_t bullets) {_bullets = bullets;} 
    const int bullets() const {return _bullets;} 

    static const uint16_t MAX_BULLETS = 17; 

protected: 
    int _bullets; 
}; 

class Soldier 
{ 
public: 
    Soldier(const string &name, const Gun &gun) : _name(name), _gun(gun) {} 
    virtual ~Soldier() {} 
    const string& name() const; 
    Gun& gun() {return _gun;} 

protected: 
    string _name; 
    Gun _gun; 
}; 


int main (int argc, char const *argv[]) 
{ 
    Gun gun; // initialize 
    string name("Foo"); 
    Soldier soldier(name, gun); 

    soldier.gun().load(Gun::MAX_BULLETS); 

    for(size_t i = 0; i < Gun::MAX_BULLETS; ++i) 
    { 
    soldier.gun().fire(); 
    cout << "I have " << soldier.gun().bullets() << " left!" << endl; 
    } 
    return 0; 
} 
Cuestiones relacionadas