2010-07-15 24 views
8

Aunque he estado programando durante bastante tiempo, cuando se trata de acoplar objetos, siempre me golpeo la cabeza contra la pared, así que me pregunto si alguien tiene recursos o reglas de oro. Puedo seguirParadigma orientado a objetos Pregunta

Permítanme dar un pequeño ejemplo, en ningún idioma en particular ...

class Person { 
    private int personnel_id 
    private String first_name; 
    private String last_name; 
    private int personnel_level; 
    //Lab labs[4]; <- Lab(s) the Person works in 
} 

class Lab { 
    private int lab_id; 
    private String lab_name; 
    //Person[99] personnel; <- Person(s) working in the Lab 
} 

Permite ignorar ctors/set/getters/dtors por ahora y solo una instancia algunas cosas ...

Person people = new Person[1500]; 
Lab labs = new Lab[10]; 

Mi pregunta es .. ¿cuál es la mejor práctica aquí ...

people["Gordon Freeman"].blewUp((Lab)"Black Mesa"); 
-> returns T/F 

o ...

labs["BlackMesa"].blownUpBy((Person)"Gordon Freeman"); 
-> returns T/F 

o tal vez ni siquiera le importa: S

El ejemplo de la vida real que estoy trabajando es una tonelada más compleja. Cada vez que el Person hace algo, todos en el Lab deben ser notificados, etc., y estoy tratando de descubrir si hay algún principio que pueda aplicar aquí.

+0

¿Qué es * devuelve T/F *? – OscarRyz

+0

Supongo que devuelve verdadero o falso? –

+0

Sí, lo siento T/F = devuelve verdadero o falso. –

Respuesta

3

Mi respuesta es una combinación de varias respuestas existentes.

El problema esencial aquí es que aquí hay un concepto oculto. El método no está hablando realmente de objeto de laboratorio o persona objeto, sino de la relación entre ellos. (Según lo sugerido por @dacris y @vs.)

Una forma de hacer frente a estas situaciones es el uso de un lenguaje con doble expedición (Gracias, @Ken.)

Otra forma, es tener auto código generado (gracias @vs.) en cuyo caso habría métodos disponibles en cualquier dirección.

Pero a menudo esas soluciones no son prácticas; cambiar idiomas enteros parece exagerado.

La solución autogenerada nos da una idea. Ambas técnicas deben ser legales. Entonces puedes implementar ambas técnicas manualmente.

Sin embargo, si no desea repetirlo, este enfoque deja en claro que CUALQUIERA de las instrucciones es legal. Así que no te preocupes demasiado.

Si está codificando un sistema donde el objeto Person tiene otros usos además de explosionar cosas, sería mejor que el acoplamiento pase de Lab a Person (es decir, ponga los métodos en el objeto Lab) para que el objeto Person pueda ser utilizado en otro lugar sin tener que lidiar con los cambios en el objeto Lab o los métodos relacionados con la explosión.

... y viceversa. Si todo lo que hace una persona es explotar las cosas, entonces la lógica debería estar ahí para mantener el laboratorio limpio y prístino (¡lo cual es importante para los laboratorios!)

1

Piensa como si estuvieras hablando inglés. La regla general es que los verbos (y los métodos) deben tener "voz activa" tanto como sea posible, es decir, un objeto debe hacer algo, en lugar de hacer algo con él.

Si se trata de un evento, la voz pasiva tiene más sentido: el laboratorio debe saber qué personas hay en ella, pero una persona aleatoria (incluso una que trabaje en el mismo laboratorio) probablemente no debería, por lo que una notificación El laboratorio estalló sería mejor venir del laboratorio en sí. Pero realmente, se trata de preferencias personales (o de equipo) en ese caso.

+0

No estoy de acuerdo con la primera parte. Las reglas de inglés se pueden usar para encontrar buenos nombres de método, uno de los cuales se determinan las responsabilidades, pero no deben dictar responsabilidades de objetos. – Oddthinking

+1

Dictar, no, pero tienden a ir de la mano. Un objeto debe actuar, en lugar de actuar sobre él, cuando sea posible. Esa es parte del pensamiento detrás de OOP. – cHao

+0

Si dice lab.explode (culpable) el objeto de laboratorio está actuando, en lugar de actuar en consecuencia. Si dices culpable.explode (lab) el objeto persona está actuando, en lugar de actuar sobre él. No ayuda a distinguir en este caso. – Oddthinking

0

Creo que está relacionado con el mundo real y su convención de codificación en lugar de buenas prácticas generales. Para su inglés, todavía prefiero llamar a people.notify (lab). Sin embargo, si quiere que su laboratorio tenga datos sobre quién lo llama, qué persona, puede hacer lab.isNotifiedBy (personas).

La buena práctica aquí es que tiene sentido para usted y su colega al mirar el código, entienden lo que hace, si quieren encontrar un método, saben dónde deben comenzar en lugar de simplemente seguir buscando o pidiéndole que

0

me gusta el diseño de este tipo de cosas:

let blackMesa = labs["BlackMesa"] 
if (blackMesa.isDestroyed) 
{ 
    let destroyer = blackMesa.destroyer 
} 
2

Es posible que desee leer un poco sobre el observador y publicación/suscripción patrones. Lo que describes es más o menos la aplicación clásica para el patrón Observer. El patrón pub/sub es básicamente la misma idea abstraída un poco más para ayudar a escalar.

En cualquier caso, dado lo bien conocido que ya es este patrón, es mejor que siga su convención a menos que encuentre una situación en la que esté realmente seguro de que se beneficiará de lo contrario.

+0

Hmmm ... Así que está sugiriendo que los observadores de personas de laboratorio, por lo que se les puede decir cuando están volados, pero todavía deja la cuestión de cómo se desencadena la explosión (por así decirlo) - por ejemplo, lab1.exploded (culpable) o culpable.explode (lab1). – Oddthinking

+0

Me gusta mucho pubsub, pero como dijo @Oddthinking, el "problema" del acoplamiento sigue ahí. Quizás es solo como funciona mi cerebro. Al final, casi obtienes el mismo resultado, pero por ejemplo ... cuando dejo una habitación en la vida real. Yo solo ... salgo de la habitación. La habitación no tiene que "perderme" ... ni la habitación tiene que decirle a todos los demás en la sala que "David se fue". Tal vez estoy siendo TOC al respecto sin una buena razón, he escrito este tipo de código millones de veces antes de> _ < –

0

En este caso me gustaría introducir un nuevo objeto - LabExplosion

class LabExplosion 
{ 
    private Person personResponsible; 
    private Lab labAffected; 
} 

A continuación, mantenga un repositorio de LabExplosions en algún lugar, y hacer algo como:

// To find out if Gordon Freeman ever blew up Black Mesa 
explosions.find("Gordon Freeman", "Black Mesa").length > 0; 
// returns T/F 
+0

y supongo que crear una nueva LabExplosion cada vez que alguien explota un laboratorio? –

1

no soy completamente seguro de lo que significa su ejemplo, pero un

Un excelente libro que tiene lo que quiere en él es Applying UML and Patterns por Craig Larman.

El libro habla extensamente sobre la asignación de responsabilidades. Por ejemplo, puede usar el patrón Information Expert, en cuyo caso, el objeto que tenga más conocimiento de las variables involucradas será el que tenga la responsabilidad de tener el método.

1

Tienes razón. Creo que este es uno de los principales problemas de la mayoría de los sistemas actuales orientados a objetos: a menudo, los métodos parecen pertenecer naturalmente a un objeto, pero a menudo no lo hacen.

Los sistemas con multiple dispatch cuidadosamente evitan este problema. Por ejemplo, en Dylan, se podría decir algo como:

define method blows-up(p :: <person>, l :: <lab>) => explodes :: <boolean>; 
    // ...returns #f or #t... 
end method; 

(I ligado a la página de métodos múltiples c2.com porque creo que hace la menos mala trabajo de describir esta Wikipedia tiene una página para Multiple_Dispatch,. pero su ejemplo es bastante horrible.)

0

cuál es la mejor práctica aquí ...

depende de su use case, ¿cómo está el usuario va a utilizar el sistema ?. ¿Sería un laboratorio "explotado" por un Person? o el caso de uso del sistema es tener algunos Person volar Labs?

o tal vez ni siquiera le importa: S

Al final el resultado es el mismo, pero lo importante aquí es la semántica del código. Si suena tonto que la gente explote Labs, entonces no lo hagas.

Así que la regla de oro, como mencionó BobTurbo, es encontrar al "experto en información" (ver: GRASP) en el sistema y darle el control a ese objeto.

Por lo general, se define una historia de usuario o narrativa de cómo se utilizaría el sistema, si, por ejemplo, la narrativa es:

Cuando una persona hace algo que todos en el laboratorio tiene que ser notificado.

Entonces, para mí significa que un Person obras en un Lab, cuando se crea, esa persona puede recibir el laboratorio trabaja en, y registrar a sí mismo para ser notificado de lo que ocurre en ese laboratorio perticula.

Desde el laboratorio tiene la lista de las personas que informen, tiene sentido de ser el laboratorio que realiza la notificación (persona da un control de laboratorio en este caso)

Entonces probablemente el Person se podría definir como:

labs.Person { 
    - name: String 
    - lab : Lab 

    + Person(withLab: Lab , andName: String) { 
      self.lab = withLab 
      self.name = andName 
      self.lab.subscribe(self) // want to know what happens 
     } 


    + blowUpLab() { 
      lab.boom!(blownBy:self) 
     } 
     // receive a lab even notification 
     // performed by "someone" 
    + labEvent(lab:Lab, by: Person ) { 
      // if is my lab and it wasn't me? 
      if(self.labg .== lab .&& self .!= by) { 
      // ok it was someone else.... 
      } 
     } 
    } 

por lo tanto, la persona que hace algo en el laboratorio, en este caso el método público blowUpLab que acaba explota el laboratorio de la persona invocando el método del laboratorio boom!.

A su vez el Lab realice las acciones del procedimiento y notificar a todos sus suscriptores al final:

labs.Lab { 
    - labName:String 
    - subscribers: Person[0..*] 

    + subscribe(to: Person) { 
      subscribers.add(to) 
     } 

    + boom!(blowedBy: Person) { 
     // do blow up the lab 
     .... 
     // and then notify: 
     subscriber.forEach(person: Person) { 
      person.labEvent(self, blowedBy) 
     } 
    } 
} 

Este es el patrón de observador.

Finalmente su aplicación principal creará las personas y de los laboratorios y ejecutar el caso de uso:

labs.MainApp { 
    _ main() { 
      blackMesaLab = Lab("BlackMesa") 
      gordon = Person(withLab: blackMesaLab, andName: "Gordon Freeman") 
      scott = Person(withLab: blackMesaLab, andName: "Scott Tiger") 
      james = Person(withLab: Lab("SO Labs"), andName:" James Hetfield"); 

      persons = Array(gordon, scott, james) 

      .... 

     while(true) { 
       // every now and then, have someone blowing up it's lab 
       if (randomNumber() .== 42) { 
        person.at(randomPosition).blowUpLab() 
      } 
     } 
     } 
    } 

Esta aplicación principal, creará tres personas, con un poco de práctica de laboratorio, sólo dos de ellos están relacionados (Scott y Gordon)

Aleatoriamente uno de ellos recibirá el mensaje blowUpLab y realizará el método. El laboratorio, a su vez, notificará a todos los suscriptores de ese laboratorio.

Así, cuando James Hetfield, un golpe de su laboratorio, nadie va a ser notificado :)

El punto es Do Describe su caso de uso, e identificar el experto en información allí; darle el control a ese objeto, y dejar que ese objeto confíe el control a otro objeto, pero solo de acuerdo con su caso de uso

Espero que tenga sentido.

1

oO le dan una perspectiva diferente sobre esto: en realidad no le interesan personas o laboratorios, pero en una relación entre ellos. Si lo miras desde una perspectiva de UML o de base de datos, verás que esta relación es un concepto muy nuevo en tu modelo (mental). Vea el comentario de @dacris arriba también, donde presenta una nueva clase.

Si desea utilizar ORM (Object-Relational Mapping), como cuando se haría cuando la ingeniería con los modelos UML, estos dos métodos blowsUp() y blownUpBy() serían automáticamente generada en código, con sus respectivos controles de tiempo de ejecución para asegurar su consistencia.

El libro de Larman debería contener algo sobre este tema para usted.

Cuestiones relacionadas