¿Está bien que una clase use su propio método público?


23

Fondo

Actualmente tengo una situación en la que tengo un objeto que es transmitido y recibido por un dispositivo. Este mensaje tiene varias construcciones, como sigue:

public void ReverseData()
public void ScheduleTransmission()

El ScheduleTransmissionmétodo debe llamar al ReverseDatamétodo siempre que se llame. Sin embargo, hay momentos en los que tendré que llamar ReverseDataexternamente (y debería agregarlos fuera del espacio de nombres por completo ) desde donde se instancia el objeto en la aplicación.

En cuanto a la "recepción" quiero decir que ReverseDatase llamará externamente en un object_receivedcontrolador de eventos para anular la inversión de los datos.

Pregunta

¿Es generalmente aceptable que un objeto llame a sus propios métodos públicos?


55
No hay nada de malo en llamar a un método público por sí mismo. Pero, según los nombres de sus métodos, ReverseData () me parece un poco peligroso ser un método público si invierte los datos internos. ¿Qué pasaría si ReverseData () llamara fuera del objeto y luego volviera a llamar con ScheduleTransmission ()?
Kaan

@Kaan Estos no son los nombres reales de mis métodos, pero están estrechamente relacionados. De hecho, "datos inversos" solo invierte 8 bits de la palabra total y se realiza cuando recibimos y transmitimos.
Snoop

La pregunta sigue en pie. ¿Qué sucede si esos 8 bits se invierten dos veces antes de programar la transmisión? Esto se siente como un gran agujero en la interfaz pública. Pensar en la interfaz pública como menciono en mi respuesta podría ayudar a sacar a la luz este problema.
Daniel T.

2
@ StevieV Creo que esto solo amplifica la preocupación que Kaan plantea. Parece que está exponiendo un método que cambia el estado del objeto, y el estado depende principalmente de cuántas veces se llame al método. Esto lo convierte en una pesadilla al tratar de realizar un seguimiento del estado del objeto en todo el código. Me parece más como si se beneficiara de tipos de datos separados de estos estados conceptuales, por lo que puede saber qué hay en cualquier parte del código sin tener que preocuparse por eso.
jpmc26

2
@StevieV algo así como Datos y Datos serializados. Los datos son lo que ve el código y SerializedData es lo que se envía a través de la red. Luego haga que los datos inversos (o más bien serializar / deserializar datos) se transformen de un tipo a otro.
csiz

Respuestas:


33

Yo diría que no solo es aceptable, sino que se recomienda especialmente si planea permitir extensiones. Para admitir extensiones de la clase en C #, deberá marcar el método como virtual según los comentarios a continuación. Sin embargo, es posible que desee documentar esto, para que alguien no se sorprenda cuando anule ReverseData () y cambie la forma en que funciona ScheduleTransmission ().

Realmente se reduce al diseño de la clase. ReverseData () suena como un comportamiento fundamental de su clase. Si necesita usar este comportamiento en otros lugares, probablemente no quiera tener otras versiones de él. Solo debe tener cuidado de no dejar que los detalles específicos de ScheduleTransmission () se filtren en ReverseData (). Eso creará problemas. Pero como ya está usando esto fuera de la clase, probablemente ya lo haya pensado detenidamente.


Wow, me pareció realmente extraño, pero esto ayuda. Me gustaría ver también lo que dicen otras personas. Gracias.
Snoop

2
"para que alguien no se sorprenda al anular ReverseData () cambia la forma en que ScheduleTransmission () funciona" : no, en realidad no. Al menos no en C # (tenga en cuenta que la pregunta tiene una etiqueta C #). A menos que ReverseData()sea ​​abstracto, no importa lo que haga en la clase secundaria, siempre se llamará al método original.
Arseni Mourzenko

2
@MainMa O virtual... Y si está anulando el método, entonces, por definición, debe ser virtualo abstract.
Pokechu22

1
@ Pokechu22: de hecho, virtualtambién funciona. En todos los casos, la firma, según OP, es public void ReverseData(), por lo que la parte de la respuesta sobre la anulación de cosas es ligeramente engañosa.
Arseni Mourzenko

@MainMa, ¿está diciendo que si un método de clase base llama a un método virtual, no se comporta de la manera virtual habitual a menos que lo sea abstract? Busqué respuestas en abstractvs virtualy no vi lo mencionado: más bien abstracto solo significa que no hay una versión dada en esta base.
JDługosz

20

Absolutamente.

La visibilidad de un método tiene el único propósito de permitir o denegar el acceso a un método fuera de la clase o dentro de una clase secundaria; Los métodos públicos, protegidos y privados siempre se pueden llamar dentro de la propia clase.

No hay nada de malo en llamar a métodos públicos. La ilustración en su pregunta es el ejemplo perfecto de una situación en la que tener dos métodos públicos es exactamente lo que debe hacer.

Sin embargo, puede estar atento a los siguientes patrones:

  1. Un método Hello()llama World(string, int, int)así:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Evita este patrón. En su lugar, use argumentos opcionales . Los argumentos opcionales facilitan la detección: la persona que llama que escribe Hello(no necesariamente sabrá que existe un método que hace posible llamar al método con valores predeterminados. Los argumentos opcionales también se documentan automáticamente. World()no muestra al llamante cuáles son los valores predeterminados reales.

  2. Un método Hello(ComplexEntity)llama World(string, int, int)así:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    En su lugar, use sobrecargas . La misma razón: mejor capacidad de descubrimiento. La persona que llama puede ver de inmediato todas las sobrecargas a través de IntelliSense y elegir la correcta.

  3. Un método simplemente llama a otros métodos públicos sin agregar ningún valor sustancial.

    Pensar dos veces. ¿Realmente necesitas este método? ¿O debería eliminarlo y dejar que la persona que llama invoque los diferentes métodos? Una pista: si el nombre del método no parece correcto o es difícil de encontrar, probablemente debería eliminarlo.

  4. Un método valida la entrada y luego llama al otro método:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    En cambio, Worlddebe validar sus parámetros en sí mismo o ser privado.


¿Se aplicarían los mismos pensamientos si ReverseData fuera realmente interno y se usara en todo el espacio de nombres que contiene instancias de "objeto"?
Snoop

1
@StevieV: sí, por supuesto.
Arseni Mourzenko

@MainMa No entiendo cuál es la razón detrás de los puntos 1 y 2
Kapol

@Kapol: gracias por sus comentarios. Edité mi respuesta agregando una explicación sobre los dos primeros puntos.
Arseni Mourzenko

Incluso en el caso 1, generalmente prefiero sobrecargas en lugar de argumentos opcionales. Si las opciones son tales que el uso de sobrecargas no es una opción o produce un número especialmente grande de sobrecargas, crearía un tipo personalizado y lo usaría como argumento (como en la sugerencia 2), o refactorizaría el código.
Brian

8

Si algo es público, cualquier sistema puede llamarlo en cualquier momento. ¡No hay razón para que no puedas ser uno de esos sistemas también!

En bibliotecas altamente optimizadas, desea copiar el idioma presentado java.util.ArrayList#ensureCapacity(int).

asegurarCapacidad

  • ensureCapacity es público
  • ensureCapacity tiene todos los límites necesarios de verificación y valores predeterminados, etc.
  • ensureCapacity llamadas ensureExplicitCapacity

sureCapacityInternal

  • ensureCapacityInternal es privado
  • ensureCapacityInternal tiene una comprobación mínima de errores porque todas las entradas provienen del interior de la clase
  • ensureCapacityInternal TAMBIÉN llama ensureExplicitCapacity

sureExplicitCapacity

  • ensureExplicitCapacity TAMBIÉN es privado
  • ensureExplicitCapacityno tiene comprobación de errores
  • ensureExplicitCapacity hace trabajo real
  • ensureExplicitCapacityno se llama desde ninguna parte excepto por ensureCapacityyensureCapacityInternal

De esta manera, el código en el que confía obtiene acceso privilegiado (¡y más rápido!) Porque sabe que sus entradas son buenas. El código en el que no confía pasa por cierto rigor para verificar su integridad y bombardear, proporcionar valores predeterminados o manejar entradas incorrectas. Ambos canalizan al lugar que realmente funciona.

Sin embargo, esto se usa en ArrayList, una de las clases más utilizadas en el JDK. Es muy posible que su caso no requiera ese nivel de complejidad y rigor. En pocas palabras, si todas las ensureCapacityInternalllamadas se reemplazaran por ensureCapacityllamadas, el rendimiento aún sería muy, muy bueno. Esta es una microoptimización probablemente solo realizada después de una extensa consideración.


3

Ya se han dado varias buenas respuestas, y también estaría de acuerdo con ellas en que sí, un objeto puede llamar a sus métodos públicos desde sus otros métodos. Sin embargo, hay una leve advertencia de diseño que deberá tener en cuenta.

Los métodos públicos generalmente tienen un contrato de "tomar el objeto en un estado consistente, hacer algo sensato, dejar el objeto en un estado consistente (posiblemente diferente)". Aquí, "coherente" puede significar, por ejemplo, que el Lengthde a List<T>no es mayor que su Capacityy que no hace referencia a los elementos en los índices de 0a Length-1.

Pero dentro de los métodos del objeto, el objeto puede estar en un estado inconsistente, por lo que cuando llama a uno de sus métodos públicos, puede hacer algo muy incorrecto, porque no fue escrito con esa posibilidad en mente. Entonces, si planea llamar a sus métodos públicos desde sus otros métodos, asegúrese de que su contrato sea "tomar el objeto en algún estado, hacer algo sensato, dejar el objeto en un (posiblemente diferente) (tal vez inconsistente, pero solo si la inicial estado era inconsistente) estado ".


1

Otro ejemplo cuando llamar al método público dentro de otro método público es totalmente correcto es un enfoque CanExecute / Execute. Lo uso cuando necesito validación y preservación invariante .

Pero en general siempre soy cauteloso al respecto. Si un método a()se llama método interno b(), significa que ese método a()es un detalle de implementación de b(). Muy a menudo indica que pertenecen a diferentes niveles de abstracción. Y el hecho de que ambos sean públicos me hace preguntarme si es una violación del principio de responsabilidad única o no.


-5

Lo siento, pero voy a tener que estar en desacuerdo con la mayoría de las otras respuestas 'sí, puedes' y decir eso:

Desalentaría una clase que llama un método público de otro

Hay un par de posibles problemas con esta práctica.

1: bucle infinito en clase hertited

Entonces, su clase base llama al método1 del método2, pero luego usted u otra persona hereda de él y oculta el método1 con un nuevo método que llama al método2.

2: Eventos, registro, etc.

por ejemplo, tengo un método Add1 que dispara un evento '¡1 agregado!' Probablemente no quiero que el método Add10 genere ese evento, escriba en un registro o lo que sea, diez veces.

3: roscado y otros puntos muertos

Por ejemplo, InsertComplexData abre una conexión db, inicia una transacción, bloquea una tabla, luego llama a InsertSimpleData, con abre una conexión, inicia una transacción, espera a que la tabla se desbloquee ...

Estoy seguro de que hay más razones, una de las otras respuestas tocó 'editas el método1 y te sorprende que el método2 comience a comportarse de manera diferente'

En general, si tiene dos métodos públicos que comparten código, es mejor hacer que ambos llamen a un método privado en lugar de que uno llame al otro.

Editar ----

Vamos a ampliar el caso específico en el OP.

no tenemos muchos detalles, pero sabemos que ReverseData es llamado por un controlador de eventos de algún tipo, así como por el método ScheduleTransmission.

Supongo que los datos inversos también cambian el estado interno del objeto

Dado este caso, pensaría que la seguridad del hilo sería importante y, por lo tanto, se aplica mi tercera objeción a la práctica.

Para hacer que el hilo ReverseData sea seguro, puede agregar un bloqueo. Pero si ScheduleTransmission también necesita ser seguro para subprocesos, querrá compartir el mismo bloqueo.

La forma más fácil de hacerlo es mover el código ReverseData a un método privado y hacer que ambos métodos públicos lo llamen. Luego puede colocar la declaración de bloqueo en los Métodos públicos y compartir un objeto de bloqueo.

Obviamente puedes argumentar "¡eso nunca sucederá!" o "Podría programar el bloqueo de otra manera", pero el punto sobre las buenas prácticas de codificación es estructurar bien su código en primer lugar.

En términos académicos, diría que esto viola la L en sólido. Los métodos públicos son más que solo accesibles públicamente. También son modificables por sus herederos. Su código debe cerrarse para su modificación, lo que significa que debe pensar qué trabajo realiza en los métodos públicos y protegidos.

Aquí hay otro: también violas potencialmente DDD. Si su objeto es un objeto de dominio, sus métodos públicos deben ser términos de dominio que significan algo para la empresa. En este caso, es muy poco probable que 'comprar una docena de huevos' sea lo mismo que 'comprar 1 huevo 12 veces', incluso si comienza de esa manera.


3
Estos problemas suenan más como una arquitectura mal pensada que como una condena general del principio de diseño. (Tal vez llamar "1 agregado" está bien siempre. Si no lo está, deberíamos programar de manera diferente. Tal vez si dos métodos están tratando de bloquear de forma exclusiva el mismo recurso, no deberían llamarse entre ellos, o los escribimos mal .) En lugar de presentar malas implementaciones, es posible que desee centrarse en argumentos más sólidos que aborden el acceso a los métodos públicos como un principio universal. Por ejemplo, dado un buen código ordenado, ¿por qué es malo?
doppelgreener

Exactamente, si no es así, no tienes un lugar para organizar el evento. Del mismo modo, con el n. ° 3, todo está bien hasta que uno de sus métodos en la cadena necesite una transacción, entonces está jodido
Ewan

Todos estos problemas reflejan más que el código en sí es de baja calidad que el principio general que es defectuoso. Los tres casos son simplemente código incorrecto. Pueden resolverse con alguna versión de "no hagas eso, haz esto en su lugar" (quizás preguntando "¿ qué es lo que quieres exactamente?"), Y no cuestiones el principio general de una clase que llama a su propio público métodos El vago potencial para un bucle infinito es el único aquí que aborda el asunto como una cuestión de principio, e incluso entonces no se aborda a fondo.
doppelgreener

Lo único que el 'código' en mi ejemplo comparte es un método público que llama a otro. Si crees que es 'código malo' bueno! Esa es mi opinión
Ewan

Que puedas usar un martillo para romperte la mano no significa que el martillo sea malo. Significa que no debes hacer cosas que te rompan la mano.
doppelgreener
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.