2010-04-16 13 views
9

Me pregunto si debería cambiar la arquitectura del software de uno de mis proyectos.¿Es una buena práctica anular la funcionalidad heredada que no se usará?

Estoy desarrollando un software para un proyecto en el que dos lados (de hecho, un host y un dispositivo) usan código compartido. Eso ayuda porque los datos compartidos, p. las enumeraciones se pueden almacenar en un lugar central.

Estoy trabajando con lo que llamamos un "canal" para transferir datos entre el dispositivo y el host. Cada canal debe implementarse en el dispositivo y el lado del host. Tenemos diferentes tipos de canales, ordinarios y canales especiales que transfieren datos de medición.

Mi solución actual tiene el código compartido en una clase base abstracta. A partir de ahí, el código se divide entre los dos lados. Como ha resultado, hay algunos casos en los que habríamos compartido código pero no podemos compartirlo, tenemos que implementarlo en cada lado.

El principio de DRY (no te repitas) dice que no deberías tener código dos veces.

Mi idea era concatenar la funcionalidad de p. Ej. el canal de medición abstracto en el lado del dispositivo y el lado del host en una clase abstracta con código compartido. Sin embargo, eso significa que una vez que creamos una clase real para el dispositivo o el lado del host para ese canal, tenemos que ocultar la funcionalidad que utiliza el otro lado.

es esto una cosa aceptable para hacer:

public abstract class ChannelAbstract 
{ 
    protected void ChannelAbstractMethodUsedByDeviceSide() { } 
    protected void ChannelAbstractMethodUsedByHostSide()  { } 
} 

public abstract class MeasurementChannelAbstract : ChannelAbstract 
{ 
    protected void MeasurementChannelAbstractMethodUsedByDeviceSide() { } 
    protected void MeasurementChannelAbstractMethodUsedByHostSide()  { } 
} 

public class DeviceMeasurementChannel : MeasurementChannelAbstract 
{ 
    public new void MeasurementChannelAbstractMethodUsedByDeviceSide() 
    { 
     base.MeasurementChannelAbstractMethodUsedByDeviceSide(); 
    } 

    public new void ChannelAbstractMethodUsedByDeviceSide() 
    { 
     base.ChannelAbstractMethodUsedByDeviceSide(); 
    } 
} 

public class HostMeasurementChannel : MeasurementChannelAbstract 
{ 
    public new void MeasurementChannelAbstractMethodUsedByHostSide() 
    { 
     base.MeasurementChannelAbstractMethodUsedByHostSide(); 
    } 

    public new void ChannelAbstractMethodUsedByHostSide() 
    { 
     base.ChannelAbstractMethodUsedByHostSide(); 
    } 
} 

Ahora, DeviceMeasurementChannel está utilizando sólo la funcionalidad para el lado del dispositivo de MeasurementChannelAbstract. Al declarar todos los métodos/miembros de MeasurementChannelAbstract protected, debe usar la palabra clave new para permitir el acceso a esa funcionalidad desde el exterior.

¿Eso es aceptable o hay algún inconveniente, advertencia, etc. que pueda surgir más tarde al usar el código?

+0

¿Has dicho en varios lugares que comparten código y luego en varios otros lugares en los que has tenido que implementar algo por separado? ¿Qué es exactamente lo que se comparte entre ellos que te impide dividirlos? –

+0

@Jon Cage: en este momento (antes de cambiar la arquitectura sw al ejemplo anterior) tengo DeviceMeasurementChannelAbstract y HostMeasurementChannelAbstract. Uno para el lado del dispositivo y otro para el lado del host. Los dos canales heredan de DeviceChannelAbstract y de HostChannelAbstract, respectivamente. Estos dos heredan de una clase abstracta compartida llamada ChannelAbstract. Ahora tengo dos variables que todos los canales de medición deben tener pero no los canales ordinarios. Ese es el problema. Espero que esto lo aclare más de lo que te confundió: S –

Respuesta

5

Para mí, parece un poco como si estuvieras confundiendo la herencia y la composición. Cuando tiene que "en blanco"/arrojar una excepción cuando la funcionalidad heredada no se solapa sanamente, su gráfico de herencia no tiene alguna clase intermedia. Y a menudo esto se debe a que algunas funciones solo deberían provenir de una instancia miembro de otra clase en lugar de heredarse.

Considere también la practicidad, el mapeo de todo en OOP perfecto no es el objetivo, el objetivo es un programa de trabajo que se puede mantener sin grandes dolores.

+0

@Pasi Savolainen: Por el momento no puedo pensar en otra manera de asegurarme de que el código no se repita en el host/dispositivo aparte de tener clases base abstractas compartidas que tienen funcionalidad para cada lado. Bajo la afirmación de que la facilidad de mantenimiento tiene que ser fácil, tendría que recurrir a mi enfoque antes mencionado de "borrar" la funcionalidad que no es apropiada para el lado respectivo (dispositivo/host). De esa manera, al menos puedo asegurarme de que la implementación de un canal en el lado del dispositivo/host herede de la misma clase base, compartiendo información. –

+0

Acepté su respuesta porque creo que se acerca más a la situación que enfrentamos aquí. Solo para que quede constancia, nos mantenemos con nuestro enfoque actual donde dividimos las clases para la implementación del dispositivo/host desde el principio para evitar la confusión que conllevaría mantener el código para ambos lados en la misma clase. No fue una decisión fácil, pero pensando en otros desarrolladores que intentan entender el código, creo que esta es la mejor decisión. No se asigna a OOP perfecto, pero funciona y es más claro. –

0

Me parece que no ha terminado de identificar qué bits de código se comparten. ¿No tendría más sentido mantener todo el material genérico/compartido en MeasurementChannelAbstract en lugar de tener diferentes métodos que las dos partes llaman? ¿Pensaría que deberían estar en clases heredadas?

7

Puede resolver el problema con la herencia, así:

public abstract class MeasurementChannelAbstract 
{ 
    protected abstract void Method(); 
} 

public class DeviceMeasurementChannel : MeasurementChannelAbstract 
{ 
    public void Method() 
    { 
     // Device side implementation here. 
    } 
} 

public class HostMeasurementChannel : MeasurementChannelAbstract 
{ 
    public void Method() 
    { 
     // Host side implementation here. 
    } 
} 

... o por la composición, utilizando el patrón de estrategia, así:

public class MeasurementChannel 
{ 
    private MeasurementStrategyAbstract m_strategy; 

    public MeasurementChannel(MeasurementStrategyAbstract strategy) 
    { 
     m_strategy = strategy; 
    } 

    protected void Method() 
    { 
     m_strategy.Measure(); 
    } 
} 

public abstract class MeasurementStrategyAbstract 
{ 
    protected abstract void Measure(); 
} 

public class DeviceMeasurementStrategy : MeasurementStrategyAbstract 
{ 
    public void Measure() 
    { 
     // Device side implementation here. 
    } 
} 

public class HostMeasurementStrategy : MeasurementStrategyAbstract 
{ 
    public void Measure() 
    { 
     // Host side implementation here. 
    } 
} 

Me parece que usted desea dividir su jerarquía de herencia entre ambos Canales estándar/de medida y Canales de dispositivo/host. Una forma de hacerlo es con la herencia múltiple, pero C# no admite la herencia múltiple (a excepción de las interfaces), y en la mayoría de los casos, un diseño basado en la composición será más simple.

+0

@richj: Creo que tienes ese derecho. Tengo canales estándar y de medición (mientras que un canal de medición amplía la funcionalidad del canal de medición). También necesito tener implementaciones separadas para el lado del dispositivo y el lado del host. Como dijiste, C# no hace herencia múltiple. Por lo tanto, me pregunto si está bien tener una sola cadena de herencia (aceptando que algunas clases tendrán funcionalidad tanto para el host como para el dispositivo) y hacer la separación en el canal de host/dispositivo en la última etapa (borrar la funcionalidad que es innecesario). –

+0

@richj: su ejemplo de resolver el problema usando herencia no funciona para mí, ya que tengo métodos en MeasurementChannelAbstract que solo se usan por el lado del dispositivo o al revés. Sé que podría moverlos a DeviceMeasurementChannelAbstract y HostMeasurementChannelAbstract, que heredarían de MeasurementChannelAbstract. El problema es que, por ejemplo, TemperatureChannel que debe implementarse para ambos lados tendría que heredarse de una de estas dos clases y no puede heredar de TemperatureChannelAbstract (donde se podrían definir los datos compartidos para este canal). –

+0

Me pregunto si sería útil abordar el problema desde el punto de partida de las interfaces en lugar de las clases abstractas. Las interfaces serían: IChannel, ISidedChannel: IChannel, IMeasurementChannel: IChannel, ISidedMeasurementChannel: ISidedChannel, IMeasurementChannel. Al usar esta idea, puede proporcionar una implementación concreta para cada variación de tipo, o puede proporcionar una implementación concreta para cada tipo y tratar las variaciones utilizando las estrategias (DeviceSideStrategy, HostSideStrategy, ...). – richj

2

Me gusta Rich implica: Necesitar solo UNO de los miembros declarados en MeasurementChannelAbstract en una de las implementaciones concretas es una indicación muy clara de que su interfaz está mal definida ya que tiene más de una responsabilidad. Esto significa que es difícil para los clientes de su código (y lectores) comprender las abstracciones y ver la diferencia entre las diversas implementaciones concretas.

Esto se llama "Single Responsibility Principle" y es muy importante para un buen diseño de OO.

(Para obtener más información sobre el buen diseño OO, recomiendo trabajar con todos los principios SÓLIDOS).

0

Por lo tanto me pregunto si está bien a tienen una única cadena de herencia (aceptando que algunas clases tendrán funcionalidad tanto para el host y el dispositivo lado) y hacer la separación en anfitrión de canal/dispositivo en la última etapa (blanking a cabo la funcionalidad de la que no es necesaria )

Creo que está bien. Pero puede borrar la funcionalidad que no es necesaria mediante el uso de diferentes interfaces para SimpleChannel y MeasurementChannel.

Cuestiones relacionadas