¿Es este un mal diseño de OOP para una simulación que involucra interfaces?

13

Estoy diseñando mi propio pequeño programa OOP para simular vampiros, lobos, humanos y camiones y estoy tratando de implementar mi propia comprensión limitada de las interfaces.

( Todavía estoy abstrayendo aquí y todavía no tengo implementación de código, así que es más bien una cuestión de diseño de OOP ... ¡creo!)

¿Estoy en lo cierto al buscar 'comportamiento común' entre estas clases e implementarlas como interfaces ?

Por ejemplo, los vampiros y los lobos muerden ... ¿entonces debería tener una interfaz de mordisco?

public class Vampire : Villain, IBite, IMove, IAttack

Del mismo modo para camiones ...

public class Truck : Vehicle, IMove

Y para los humanos ...

public class Man : Human, IMove, IDead

¿Mi pensamiento está aquí? (Aprecio tu ayuda)

usuario3396486
fuente
14
Los animales, las verduras y los minerales rara vez son buenos ejemplos para la implementación de aplicaciones. Implementaciones reales son generalmente más abstracto, como IEnumerable, IEquatable, etc.
Robert Harvey
66
Tiene una sola mención de lo que sus objetos están a punto de hacer en su software ("mordida"). El software normalmente está diseñado para hacer algo, basar un modelo de objeto solo en características no lleva a ninguna parte.
tofro
@tofro Mi intención era que IBite contendría múltiples métodos que implementarían el comportamiento con respecto a (1) La reducción del nivel de 'vida / energía' de otro (2) La aparición o invocación de gráficos de 'sangre' y (3) la actualización de las estadísticas de simulación datos (como NoOfBites). Creo que puedo apreciar que una interfaz se utiliza mejor para implementar una variedad de comportamientos de métodos.
user3396486
2
¿Las clases Human, Vampire y Vehicle ya no implementan la interfaz IMove? ¿Por qué necesita hacer que las subclases lo implementen demasiado explícitamente?
Pierre Arlaud
¿Todas estas interfaces son realmente necesarias? Afortunadamente, en Python no necesitas nada de esto, fue un cambio realmente refrescante (mi primer idioma fue Object Pascal). También los métodos virtuales pueden ser una mejor solución en algunos casos.
Ajasja

Respuestas:

33

En general, desea tener interfaces para las características comunes de su clase.

Estoy semi-de acuerdo con @Robert Harvey en los comentarios, quien dijo que generalmente las interfaces representan características más abstractas de las clases. Sin embargo, encuentro a partir de ejemplos más concretos una buena forma de comenzar a pensar en abstracto.

Si bien su ejemplo es técnicamente correcto (es decir, sí, los vampiros y los lobos muerden, por lo que puede tener una interfaz para eso), hay una cuestión de relevancia. Cada objeto tiene miles de características (por ejemplo, los animales pueden tener pelaje, nadar, trepar a los árboles, etc.). ¿Harás una interfaz para todos ellos? Muy menos probable.

Por lo general, desea interfaces para cosas que tienen sentido agruparse en una aplicación como un todo. Por ejemplo, si está creando un juego, puede tener una variedad de objetos IMove y actualizar su posición. Si no quieres hacer eso, tener la interfaz IMove es bastante inútil.

El punto es, no sobre ingeniero. Debe pensar cómo va a usar esa interfaz, y 2 clases que tienen un método en común no es una razón suficiente para crear una interfaz.

Paul92
fuente
1
Ciertamente espero que cada objeto no tenga miles de atributos.
cabeza de jardín
44
Atributos no como en los atributos oop sino en los atributos / características gramaticales (cosas como enumerables, comparables, etc.): D. Mala elección de palabras.
Paul92
3
Vale la pena señalar que las interfaces que son útiles son las que usará. Por ejemplo, IBiteno es particularmente útil, pero es posible que desee IAttackpara poder trabajar en todas las cosas que hacen ataques, o IUpdatepara que pueda ejecutar las actualizaciones para todo, o IPhysicsEnabledpara que pueda aplicarles física, etc.
anaximander
1
Esta respuesta plantea algunos puntos muy buenos. El párrafo final lo resume bastante bien; al menos tan bien como sea posible con el nivel de detalle proporcionado.
Lightness compite con Monica el
1
agrupar métodos comunes se ajusta más a las clases abstractas. La interfaz se hace para diseñar contratos que deben respetar a quienes la implementan, no agrupando la misma implementación para algunos objetos.
Walfrat
28

Parece que estás creando un montón de interfaces de método único . Esto está bien a primera vista, pero tenga en cuenta que las interfaces no son propiedad de las clases que las implementan. Son propiedad de los clientes que los usan. Los clientes deciden si algo debe ser algo que pueda moverse y atacar.

Si tengo una Combatclase con un fight()método, ese método probablemente necesite llamar a ambos move()y attack()en el mismo objeto. Esto sugiere fuertemente la necesidad de una ICombatantinterfaz que fight()puede llamar move()y attack()a través. Esto es más limpio que fight()tomar un IAttackobjeto y lanzarlo IMovepara ver si también puede moverse.

Eso no significa que no puedas tener también IMove IAttackinterfaces. Solo espero que no los hagas sin que algún cliente los necesite. Por el contrario, si ningún cliente necesita hacer que un objeto se mueva y ataque, entonces ICombatantno es necesario.

Esta forma simple de mirar las interfaces a menudo se pierde porque a las personas les gusta seguir ejemplos. Las primeras interfaces a las que estamos expuestos están en las bibliotecas. Desafortunadamente, las bibliotecas no tienen idea de cuáles son sus clientes. Por lo tanto, solo pueden adivinar las necesidades de sus clientes. No es el mejor ejemplo a seguir.

naranja confitada
fuente
1
Maldición, esto es bueno. El juego simplemente parece una muy buena manera de usar y explicar la POO.
JeffO
66
@JeffO hasta que implementes un juego razonablemente grande y te des cuenta de que OOP es un desastre y estarías mejor con sistemas basados ​​en componentes o diseños orientados a datos.
Darkhogg
"Las interfaces son propiedad de los clientes que las usan"
Tibos
1
+1 por la diferencia entre bibliotecas y aplicaciones, a menudo (¿demasiado?: /) Leo toneladas de cosas que se ajustan a una y no a la otra.
Walfrat
3

Considere si será común tener colecciones de objetos con diferentes combinaciones de habilidades, y si el código puede querer realizar una acción sobre esos elementos, dentro de una colección, que lo soportan . Si es así, y si hubiera un "comportamiento predeterminado" sensible para los objetos que no tienen soporte útil para alguna acción, puede ser útil tener interfaces implementadas por una amplia gama de clases, no solo aquellas que pueden comportarse de manera útil.

Por ejemplo, supongamos que solo unos pocos tipos de criaturas pueden tener Woozles, y uno quiere que tales criaturas tengan una NumerOfWoozlespropiedad. Si dicha propiedad estuviera en una interfaz que solo fueran implementadas por criaturas que pueden tener Woozles, entonces el código que quería encontrar el número total de Woozles en poder de una colección de criaturas de tipos mixtos tendría que decir algo como:

int total = 0;
foreach (object it in creatures)
{
   IWoozleCountable w = trycast(it, IWoozleCountable);
   if (w != null) total += w.WoozleCount;
}

Sin embargo, si WoozleCount fuera miembro de Creature / ICreature, a pesar de que pocos subtipos anularían la implementación de WoozleCount predeterminada de Creature que siempre devuelve cero, el código podría simplificarse para:

int total = 0;
foreach (ICreature it in creatures)
   total += it.WoozleCount;

Si bien algunas personas pueden irritarse ante la idea de que cada Criatura implemente una propiedad WoozleCount que realmente solo es útil para unos pocos subtipos, la propiedad sería significativa para todos los tipos, ya sea que sea útil o no con elementos que se sabe que son de esos tipos, y consideraría que la interfaz del "fregadero de la cocina" tiene menos olor a código que el operador trycast.

Super gato
fuente