c# - realizacion - ejemplos de agregacion en base de datos
C#OOP ComposiciĆ³n y GeneralizaciĆ³n al mismo tiempo (11)
Esta podría ser una pregunta simple / básica de POO, pero todavía no puedo resolver cómo resolverla. Tuve el siguiente problema durante una entrevista: haga un diagrama de clase UML y escriba el código básico para un teléfono "inteligente" que contenga las funciones de un teléfono y un reproductor de mp3. Tenemos con la siguiente solución (aceptada):
class Telephone
{
public string name { get; set; }
public Telephone()
{
name = "name telephone";
}
}
class MP3
{
public string name { get; set; }
public MP3()
{
name = "name mp3";
}
}
Y la clase de teléfono "inteligente":
class TelephoneMP3
{
public Telephone tel;
public MP3 mp3;
public TelephoneMP3()
{
tel = new Telephone();
mp3 = new MP3();
}
}
Como puede ver, tenemos una relación de composición entre las clases TelephoneMP3 y Telephone / MP3.
Pero, utilizando este código, el TelephoneMP3 no es un teléfono y el TelephoneMP3 tampoco es un MP3, lo cual no es lógico. Entonces, ¿qué cambios debo hacer para que esto sea válido? Por ejemplo, este tipo de prueba:
if (telMp3 is Telephone)
{
Console.WriteLine("TelephoneMP3 is telephone");
}
if (telMp3 is MP3)
{
Console.WriteLine("TelephoneMP3 is mp3");
}
Las modificaciones se pueden hacer usando los siguientes comentarios:
- Telephone / MP3 / TelephoneMP3 deben seguir siendo clases (los 3 de ellos)
- Podría agregar interfaces / otras clases si es necesario
- TelephoneMP3 no debe duplicar todas las funcionalidades de un Telephone / MP3 (por ejemplo, durante una herencia de una interfaz donde el TelephoneMP3 estará obligado a escribir el código de todos los miembros de la interfaz)
Gracias de antemano
¿Qué hay de esta solución:
public interface ITelephone
{
string Name{get;}
void MakeCall();
}
public interface IMp3
{
string Name { get; }
void Play(string filename);
}
public abstract class BaseTelephone : ITelephone
{
public virtual string Name { get { return "Telephone"; } }
void MakeCall()
{
// code to make a call.
}
}
public class MyMp3Player : IMp3
{
public string Name { get { return "Mp3 Player"; } }
public void Play(string filename)
{
// code to play an mp3 file.
}
}
public class SmartPhone : BaseTelephone, IMp3
{
public override string Name { get { return "SmartPhone"; } }
private IMp3 Player { get { return _Player; } set { _Player = value; } }
private IMp3 _Player = new MyMp3Player();
public void Play(string filename) { Player.Play(filename); }
}
De esta manera, el teléfono inteligente también puede ser un reproductor Mp3, pero internamente tiene un reproductor Mp3 que utiliza para reproducir la música. El reproductor interno se puede intercambiar por uno nuevo (por ejemplo, actualización) mediante el uso de la propiedad SmartPhone Player
.
El código para el teléfono solo se escribe una vez, en la clase de teléfono base. El código para el reproductor Mp3 solo se escribe una vez, también en la clase MyMp3Player.
Al contrario de todas las demás respuestas, estoy bastante seguro de que la forma en que se hace esta pregunta lo hace imposible. La razón es la siguiente :
Usted declara explícitamente
Pero, utilizando este código, el TelephoneMP3 no es un teléfono y el TelephoneMP3 tampoco es un MP3, lo cual no es lógico. Entonces, ¿qué cambios debo hacer para que esto sea válido?
Ver la palabra "es" me hace pensar inmediatamente en el operador "es". Inmediatamente asumo que esto es lo que realmente quieres.
Luego continúas para decir lo siguiente:
Telephone / MP3 / TelephoneMP3 deben seguir siendo clases (los 3 de ellos)
Bien seguro que podemos hacer lo siguiente:
interface ITelephone { }
class Telephone
{
public string name { get; set; }
public Telephone()
{
name = "name telephone";
}
}
interface IMP3 { }
class MP3 : IMP3
{
public string name { get; set; }
public MP3()
{
name = "name mp3";
}
}
class TelephoneMP3 : ITelephone, IMP3
{
public Telephone tel;
public MP3 mp3;
public TelephoneMP3()
{
tel = new Telephone();
mp3 = new MP3();
}
}
Pero todavía tenemos un problema. La palabra es". Dado que debemos mantener las clases TelephoneMP3, Telephone y MP3 y C # no es compatible con la herencia múltiple, simplemente no es posible.
Para ilustrar mi punto:
public class Program
{
static void Main(string[] args)
{
TelephoneMP3 t = new TelephoneMP3();
Console.WriteLine((t is TelephoneMP3)? true:false);
Console.WriteLine((t is ITelephone) ? true : false);
Console.WriteLine((t is IMP3) ? true : false);
Console.WriteLine((t is Telephone) ? true : false);
Console.WriteLine((t is MP3) ? true : false);
Console.ReadLine();
}
}
Esto te dará
Cierto
Cierto
Cierto
Falso
Falso
En otras palabras, TelephoneMP3 "es" un teléfono de IT. TelephoneMP3 "es" un IMP3; sin embargo, no es posible que un TelephoneMP3 sea tanto un MP3 como un teléfono.
C # no admite la herencia múltiple, necesita usar interfaces y clases abstractas para implementaciones comunes, puede hacer lo siguiente:
Editar: he añadido más detalles a mi respuesta
abstract class BaseDevice
{
public string name { get; set; }
public void Print()
{
Console.WriteLine("{0}", name );
}
}
public interface IPhone
{
void DoPhone();
}
public interface IMP3
{
void DoMP3();
}
class Telephone :BaseDevice , IPhone
{
public Telephone()
{
name = "name telephone";
}
}
class MP3 : BaseDevice , IMP3
{
public MP3()
{
name = "name mp3";
}
}
class telMp3 : BaseDevice , IMP3, IPhone
{
private Telephone _tel;
private MP3 _mp3;
public telMp3()
{
name = "name telMp3";
}
public void DoPhone()
{
_tel.DoPhone();
}
public void DoMP3()
{
_mp3.DoMP3();
}
}
Como C # no admite la herencia múltiple, considere usar interfaces en su lugar:
public interface Phone{ ... }
public interface Mp3{ ... }
public class Telephone : Phone{ ... }
public class Mp3Player : Mp3{ ... }
public class Smartphone : Phone, Mp3{ ... }
De esta manera Smartphone
es tanto Phone
como Mp3
. Si necesita escribir un método que funcione en un Telephone
, use la interfaz del Phone
. De esta manera podrás pasar el Telephone
o el Smartphone
como argumento.
Creo que esta pregunta de la entrevista no es (como deberían ser todas las preguntas de la entrevista) sobre el desafío en sí. El ejercicio de codificación de la fusión de dos clases a través de la composición podría responderse con un libro de texto. Este desafío es una pregunta de truco sutil, y propongo que el punto es hacer que usted discuta por qué . Al menos eso es lo que querría de mis entrevistados.
Esta prueba:
if(telMp3 is Telephone && telMp3 is MP3) {
... es el verdadero problema. ¿Por qué debes cumplir con estos criterios? Esta prueba anula completamente el propósito de construir objetos fuera de la composición. Exige que los objetos se implementen de una manera específica. Muestra que las implementaciones de clase existentes ya están estrechamente acopladas al código base (si no se pueden eliminar). Estos requisitos significan que no se siguieron los principios de SOLID , ya que no puede simplemente cumplir con los métodos de un tipo base, en realidad debe ser el tipo base. Eso no es bueno.
Como han dicho otras respuestas, la solución sería utilizar interfaces. Luego puede pasar su objeto a cualquier método que requiera la interfaz. Este tipo de uso requeriría una prueba como esta:
if (telMp3 is IPhone && telMp3 is IMp3) {
... pero no puedes hacer eso, debido a la limitación de tu desafío. Eso significa que allí, en el resto de su código, las personas han estado escribiendo métodos que dependen explícitamente de los tipos específicos de Telephone
y MP3
. Ese es el verdadero problema.
En mi opinión, la respuesta correcta a este desafío es decir que el código base falla la prueba. Las consecuencias específicas de tu desafío son irrelevantes; Necesita cambiar los requisitos del desafío antes de poder resolverlo correctamente. Un entrevistado que reconociera este hecho pasaría la prueba con gran éxito.
Es casi similar a las otras respuestas, pero ...
Creo que tiene la mejor precisión con respecto a la jerarquía de herencia.
internal class Program
{
private static void Main(string[] args)
{
var telephone = new Telephone();
Console.WriteLine(telephone.Name);
telephone.OutboundCall("+1 234 567");
Console.WriteLine("Am I a Telephone? {0}", telephone is Telephone);
Console.WriteLine("Am I a MP3? {0}", telephone is MediaPlayer3);
Console.WriteLine("Am I a Smartphone? {0}", telephone is Smartphone);
Console.WriteLine("Do I Have Telephone Capabilities? {0}", telephone is ITelephone);
Console.WriteLine("Do I Have MP3 Capabilities? {0}", telephone is IMediaPlayer3);
Console.WriteLine();
var mp3 = new MediaPlayer3();
Console.WriteLine(mp3.Name);
mp3.PlaySong("Lalala");
Console.WriteLine("Am I a Telephone? {0}", mp3 is Telephone);
Console.WriteLine("Am I a MP3? {0}", mp3 is MediaPlayer3);
Console.WriteLine("Am I a Smartphone? {0}", mp3 is Smartphone);
Console.WriteLine("Do I Have Telephone Capabilities? {0}", mp3 is ITelephone);
Console.WriteLine("Do I Have MP3 Capabilities? {0}", mp3 is IMediaPlayer3);
Console.WriteLine();
var smartphone = new Smartphone();
Console.WriteLine(smartphone.Name);
smartphone.OutboundCall("+1 234 567");
smartphone.PlaySong("Lalala");
Console.WriteLine("Am I a Telephone? {0}", smartphone is Telephone);
Console.WriteLine("Am I a MP3? {0}", smartphone is MediaPlayer3);
Console.WriteLine("Am I a Smartphone? {0}", smartphone is Smartphone);
Console.WriteLine("Do I Have Telephone Capabilities? {0}", smartphone is ITelephone);
Console.WriteLine("Do I Have MP3 Capabilities? {0}", smartphone is IMediaPlayer3);
Console.ReadKey();
}
public interface IDevice
{
string Name { get; }
}
public interface ITelephone : IDevice
{
void OutboundCall(string number);
}
public interface IMediaPlayer3 : IDevice
{
void PlaySong(string filename);
}
public class Telephone : ITelephone
{
public string Name { get { return "Telephone"; } }
public void OutboundCall(string number)
{
Console.WriteLine("Calling {0}", number);
}
}
public class MediaPlayer3 : IMediaPlayer3
{
public string Name { get { return "MP3"; } }
public void PlaySong(string filename)
{
Console.WriteLine("Playing Song {0}", filename);
}
}
public class Smartphone : ITelephone, IMediaPlayer3
{
private readonly Telephone telephone;
private readonly MediaPlayer3 mp3;
public Smartphone()
{
telephone = new Telephone();
mp3 = new MediaPlayer3();
}
public string Name { get { return "Smartphone"; } }
public void OutboundCall(string number)
{
telephone.OutboundCall(number);
}
public void PlaySong(string filename)
{
mp3.PlaySong(filename);
}
}
}
Salida del programa:
Telephone Calling +1 234 567 Am I a Telephone? True Am I a MP3? False AM I a Smartphone? False Do I Have Telephone Capabilities? True Do I Have MP3 Capabilities? False MP3 Playing Song Lalala Am I a Telephone? False Am I a MP3? True AM I a Smartphone? False Do I Have Telephone Capabilities? False Do I Have MP3 Capabilities? True Smartphone Calling +1 234 567 Playing Song Lalala Am I a Telephone? False Am I a MP3? False AM I a Smartphone? True Do I Have Telephone Capabilities? True Do I Have MP3 Capabilities? True
Está intentando modelar una jerarquía de productos, en la que un producto determinado puede tener sus propias propiedades específicas, además de estar compuesto por subproductos estándar. Este es ciertamente un ejemplo del patrón de composición. Sugiero introducir una interfaz base para cualquier componente del producto, luego crear interfaces específicas para productos de teléfono, reproductor de MP3 y teléfonos inteligentes.
En el patrón de composición tradicional, cada nodo puede contener una lista arbitraria de componentes a los que se pueden agregar o eliminar subcomponentes; sin embargo, en su modelo de datos parece más útil para cada tipo específico de producto especificar sus hijos precisos, luego proporcione un método genérico para iterar sobre ellos. Esto permite que los (sub) componentes específicos de un tipo / interfaz específico sean fácilmente consultables en toda la jerarquía del producto.
También introduje una interfaz para un producto GPS ya que todos los teléfonos nuevos tienen receptores GPS incorporados, solo para ilustrar cómo trabajar con jerarquías recursivas de componentes.
public interface IProductComponent
{
string Name { get; set; }
IEnumerable<IProductComponent> ChildComponents { get; }
IEnumerable<IProductComponent> WalkAllComponents { get; }
TProductComponent UniqueProductComponent<TProductComponent>() where TProductComponent : class, IProductComponent;
}
public interface ITelephone : IProductComponent
{
IGps Gps { get; }
}
public interface IMp3Player : IProductComponent
{
}
public interface IGps : IProductComponent
{
double AltitudeAccuracy { get; }
}
public interface ISmartPhone : IProductComponent
{
ITelephone Telephone { get; }
IMp3Player Mp3Player { get; }
}
Estas interfaces podrían luego ser implementadas por un conjunto paralelo de clases:
public abstract class ProductComponentBase : IProductComponent
{
string name;
protected ProductComponentBase(string name)
{
this.name = name;
}
#region IProductComponent Members
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public virtual IEnumerable<IProductComponent> ChildComponents
{
get
{
return Enumerable.Empty<IProductComponent>();
}
}
public IEnumerable<IProductComponent> WalkAllComponents
{
get
{
yield return this;
foreach (var child in ChildComponents)
{
foreach (var subChild in child.WalkAllComponents)
yield return subChild;
}
}
}
public TProductComponent UniqueProductComponent<TProductComponent>() where TProductComponent : class, IProductComponent
{
TProductComponent foundComponent = null;
foreach (var child in WalkAllComponents.OfType<TProductComponent>())
{
if (foundComponent == null)
foundComponent = child;
else
throw new Exception("Duplicate products found of type " + typeof(TProductComponent).Name);
}
return foundComponent;
}
#endregion
}
public class Telephone : ProductComponentBase, ITelephone
{
IGps gps = new Gps();
public Telephone()
: base("telephone")
{
}
#region ITelephone Members
public IGps Gps
{
get
{
return gps;
}
}
#endregion
IEnumerable<IProductComponent> BaseChildComponents
{
get
{
return base.ChildComponents;
}
}
public override IEnumerable<IProductComponent> ChildComponents
{
get
{
if (Gps != null)
yield return Gps;
foreach (var child in BaseChildComponents)
yield return child;
}
}
}
public class Gps : ProductComponentBase, IGps
{
public Gps()
: base("gps")
{
}
#region IGps Members
public double AltitudeAccuracy
{
get { return 100.0; }
}
#endregion
}
public class TelephoneMP3 : ProductComponentBase, ISmartPhone
{
ITelephone telephone;
IMp3Player mp3Player;
public TelephoneMP3()
: base("TelephoneMP3")
{
this.telephone = new Telephone();
this.mp3Player = new MP3();
}
IEnumerable<IProductComponent> BaseChildComponents
{
get
{
return base.ChildComponents;
}
}
public override IEnumerable<IProductComponent> ChildComponents
{
get
{
if (Telephone != null)
yield return Telephone;
if (Mp3Player != null)
yield return Mp3Player;
foreach (var child in BaseChildComponents)
yield return child;
}
}
#region ISmartPhone Members
public ITelephone Telephone
{
get { return telephone; }
}
public IMp3Player Mp3Player
{
get { return mp3Player; }
}
#endregion
}
public class MP3 : ProductComponentBase, IMp3Player
{
public MP3()
: base("mp3Player")
{
}
}
A medida que se agregan (o subclasifican) nuevos tipos de componentes de productos, anulan los "componentes del niño" de sus padres y devuelven sus hijos específicos del dominio.
Una vez hecho esto, puede (recursivamente) consultar la jerarquía de productos para los componentes de un tipo determinado para su uso. Por ejemplo:
var accuracy = smartPhone.UniqueProductComponent<IGps>().AltitudeAccuracy
o
bool hasPhone = (component.UniqueProductComponent<ITelephone>() != null)
Esta combinación de generalización y composición evita la duplicación de código al tiempo que hace explícito el tipo de subcomponentes que deben encontrarse en cualquier producto dado. También evita la carga de hacer que todos los productos de nivel superior representen las interfaces de sus hijos estándar, pasándoles todas las llamadas.
Hay algunas buenas respuestas aquí. Las respuestas que dicen usar interfaces son buenas, y eso es lo que probablemente busca el entrevistador. Sin embargo, consideraría simplemente negar la premisa de que la relación "es un tipo de" que se satisface es una buena idea. Más bien, consideraría usar una organización proveedora de servicios :
public interface ITelephone { ... }
internal class MyTelephone : ITelephone { ... }
public interface IMusicPlayer { ... }
internal class MyPlayer : IMusicPlayer { ... }
public interface IServiceProvider
{
T QueryService<T>() where T : class;
}
internal class MyDevice : IServiceProvider
{
MyTelephone phone = new MyTelephone();
MyPlayer player = new MyPlayer();
public T QueryService<T>() where T : class
{
if (typeof(T) == typeof(ITelephone)) return (T)(object)phone;
if (typeof(T) == typeof(IPlayer)) return (T)(object)player;
return null;
}
}
Ahora una persona que llama tiene un MyDevice
en la mano a través de su interfaz IServiceProvider
. Tu lo preguntas
ITelephone phone = myDevice.QueryService<ITelephone>();
y si el phone
no es nulo, entonces el dispositivo puede actuar como un teléfono. Pero
myDevice is ITelephone
Es falso. El dispositivo no es un teléfono, sabe cómo encontrarte algo que funcione como un teléfono .
Para obtener más información en este sentido, estudie las arquitecturas de complementos como MAF.
Podrías usar casting implícito.
class TelephoneMP3
{
public Telephone tel;
public MP3 mp3;
public TelephoneMP3()
{
tel = new Telephone();
mp3 = new MP3();
}
public static implicit operator Telephone(TelephoneMP3 telemp3) {
return telemp3.tel;
}
public static implicit operator MP3(TelephoneMP3 telemp3) {
return telemp3.mp3;
}
}
No pasará la prueba exacta que propusiste, pero puedes hacerlo
var teleMp3 = new TelephoneMP3();
Telephone t = teleMp3;
También puede utilizar implementaciones de interfaz explícitas para limitar el uso de la variable compartida Name
. De esa manera tendrías que lanzar a la interfaz para acceder a ella. Todavía puede tener propiedades / métodos públicos desde la interfaz.
La composición aún se utiliza, pero el SmartPhone
tiene control sobre las implementaciones de sus propiedades / métodos.
Para mí, esta sería la implementación más fácil para trabajar, ya que rara vez quiero usar tanto la implementación del reproductor de mp3 como el teléfono, sino una de ellas. Además, todavía tengo control total sobre lo que sucede cuando se llama a los métodos de interfaz en el SmartPhone
.
class User
{
void UseSmartPhone(SmartPhone smartPhone)
{
// Cannot access private property ''Name'' here
Console.WriteLine(smartPhone.Name);
// Cannot access explicit implementation of ''IMp3Player.Play''
smartPhone.Play();
// You can send the phone to the method that accepts an IMp3Player though
PlaySong(smartPhone);
// This works fine. You are sure to get the Phone name here.
Console.WriteLine(((IPhone)smartPhone).Name);
// This works fine, since the Call is public in SmartPhone.
smartPhone.Call();
}
void CallSomeone(IPhone phone)
{
phone.Call();
}
void PlaySong(IMp3Player player)
{
player.Play();
}
}
class SmartPhone : IPhone, IMp3Player
{
private Phone mPhone;
private Mp3Player mMp3Player;
public SmartPhone()
{
mPhone = new Phone();
mMp3Player = new Mp3Player();
}
public void Call()
{
mPhone.Call();
}
string IPhone.Name
{
get { return mPhone.Name; }
}
string IMp3Player.Name
{
get { return mMp3Player.Name; }
}
void IMp3Player.Play()
{
mMp3Player.Play();
}
}
class Mp3Player
{
public string Name { get; set; }
public void Play()
{
}
}
class Phone
{
public string Name { get; set; }
public void Call()
{
}
}
interface IPhone
{
string Name { get; }
void Call();
}
interface IMp3Player
{
string Name { get; }
void Play();
}
Usa el patrón de estrategia (usa algunos atajos a continuación, obtendrás la esencia).
public class Device {
private List<App> apps;
public Device() {
this.apps = new List<App>();
this.apps.Add(new Mp3Player());
this.apps.Add(new Telephone());
}
}
public class Mp3Player implements App {...}
public class Telephone implements App {...}
public interface App {...}
Descargo de responsabilidad: mi idioma nativo es PHP, perdóneme cualquier norma de codificación que no sea C #, etc., etc.