it-swarm-es.tech

¿Cuál es el principio de inversión de dependencia y por qué es importante?

¿Cuál es el principio de inversión de dependencia y por qué es importante?

166
Phillip Wells

Revise este documento: El principio de inversión de dependencia .

Básicamente dice:

  • Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de las abstracciones.
  • Las abstracciones nunca deben depender de los detalles. Los detalles deben depender de las abstracciones.

En cuanto a por qué es importante, en resumen: los cambios son riesgosos, y al depender de un concepto en lugar de una implementación, se reduce la necesidad de cambio en los sitios de llamadas.

Efectivamente, el DIP reduce el acoplamiento entre diferentes piezas de código. La idea es que, aunque hay muchas formas de implementar, por ejemplo, un servicio de registro, la forma en que lo usaría debería ser relativamente estable en el tiempo. Si puede extraer una interfaz que represente el concepto de registro, esta interfaz debería ser mucho más estable en el tiempo que su implementación, y los sitios de llamada deberían verse mucho menos afectados por los cambios que podría realizar al mantener o ampliar ese mecanismo de registro.

Al hacer que la implementación dependa de una interfaz, tiene la posibilidad de elegir en tiempo de ejecución qué implementación es la más adecuada para su entorno particular. Dependiendo de los casos, esto también puede ser interesante.

101
Carl Seleborg

Los libros Desarrollo de software ágil, Principios, patrones y prácticas y Principios, patrones y prácticas ágiles en C # son los mejores recursos para comprender completamente los objetivos y motivaciones originales detrás del Principio de inversión de dependencia. El artículo "El principio de la inversión de dependencia" también es un buen recurso, pero debido a que es una versión condensada de un borrador que finalmente se abrió camino en los libros mencionados anteriormente, deja de lado una discusión importante sobre el concepto de propiedad del paquete y la interfaz, que son clave para distinguir este principio del consejo más general de "programar en una interfaz, no en una implementación" que se encuentra en el libro Design Patterns (Gamma, et al.).

Para proporcionar un resumen, el principio de inversión de dependencia es principalmente acerca de revertir la dirección convencional de las dependencias de componentes de "nivel superior" a componentes de "nivel inferior" de manera que los componentes de "nivel inferior" dependen de las interfaces propiedad por los componentes de "nivel superior". (Nota: el componente de "nivel superior" se refiere al componente que requiere dependencias/servicios externos, no necesariamente su posición conceptual dentro de una arquitectura en capas). Al hacerlo, el acoplamiento no es reducido tanto como lo es desplazado de componentes que son teóricamente menos valiosos para componentes que teóricamente son más valiosos.

Esto se logra diseñando componentes cuyas dependencias externas se expresan en términos de una interfaz para la cual el consumidor del componente debe proporcionar una implementación. En otras palabras, las interfaces definidas expresan lo que necesita el componente, no cómo se usa el componente (por ejemplo, "INeedSomething", no "IDoSomething").

A lo que no se refiere el principio de inversión de dependencia es la práctica simple de abstraer dependencias mediante el uso de interfaces (por ejemplo, MyService → [ILogger ⇐ Logger]). Si bien esto desacopla un componente de la implementación específica de la dependencia, no invierte la relación entre el consumidor y la dependencia (por ejemplo, [MyService → IMyServiceLogger] ⇐ Logger.

La importancia del Principio de Inversión de Dependencia se puede resumir en un objetivo singular de poder reutilizar componentes de software que dependen de dependencias externas para una parte de su funcionalidad (registro, validación, etc.)

Dentro de este objetivo general de reutilización, podemos delinear dos subtipos de reutilización:

  1. Uso de un componente de software dentro de múltiples aplicaciones con implementaciones de subdependencia (por ejemplo, ha desarrollado un contenedor DI y desea proporcionar el registro, pero no desea acoplar su contenedor a un registrador específico de manera que todos los que usan su contenedor también tengan que utilizar su biblioteca de registro elegido).

  2. Uso de componentes de software dentro de un contexto en evolución (por ejemplo, ha desarrollado componentes de lógica de negocios que siguen siendo los mismos en múltiples versiones de una aplicación donde los detalles de la implementación están evolucionando).

Con el primer caso de reutilización de componentes a través de múltiples aplicaciones, como con una biblioteca de infraestructura, el objetivo es proporcionar una necesidad de infraestructura central a sus consumidores sin vincular a los consumidores con las dependencias dependientes de su propia biblioteca, ya que la dependencia de tales dependencias requiere su Los consumidores requieren las mismas dependencias también. Esto puede ser problemático cuando los consumidores de su biblioteca eligen usar una biblioteca diferente para las mismas necesidades de infraestructura (por ejemplo, NLog vs. log4net), o si eligen usar una versión posterior de la biblioteca requerida que no es compatible con la versión anterior. requerido por su biblioteca.

Con el segundo caso de reutilización de componentes de lógica de negocios (es decir, "componentes de nivel superior"), el objetivo es aislar la implementación del dominio central de su aplicación de las necesidades cambiantes de sus detalles de implementación (es decir, cambiar/actualizar bibliotecas de persistencia, bibliotecas de mensajería) , estrategias de encriptación, etc.). Idealmente, cambiar los detalles de implementación de una aplicación no debería romper los componentes que encapsulan la lógica de negocios de la aplicación.

Nota: Algunos pueden oponerse a describir este segundo caso como una reutilización real, razonando que los componentes como los componentes de lógica de negocios utilizados en una sola aplicación en evolución representan solo un uso. La idea aquí, sin embargo, es que cada cambio en los detalles de implementación de la aplicación genera un nuevo contexto y, por lo tanto, un caso de uso diferente, aunque los objetivos finales se pueden distinguir como aislamiento frente a portabilidad.

Si bien el Principio de inversión en dependencia en este segundo caso puede ofrecer algún beneficio, se debe tener en cuenta que su valor aplicado a lenguajes modernos como Java y C # es mucho más reducido, quizás hasta el punto de ser irrelevante. Como se mencionó anteriormente, el DIP implica la separación completa de los detalles de la implementación en paquetes separados. Sin embargo, en el caso de una aplicación en evolución, la simple utilización de las interfaces definidas en términos del dominio de negocios evitará la necesidad de modificar componentes de nivel superior debido a las necesidades cambiantes de los componentes de detalles de implementación, incluso si los detalles de implementación finalmente se encuentran dentro del mismo paquete. Esta parte del principio refleja aspectos que eran pertinentes al lenguaje en cuestión cuando se codificó el principio (es decir, C++) que no son relevantes para los idiomas más nuevos. Dicho esto, la importancia del principio de inversión de dependencia reside principalmente en el desarrollo de componentes/bibliotecas de software reutilizables.

Se puede encontrar una discusión más larga de este principio en relación con el uso simple de las interfaces, la inyección de dependencia y el patrón de interfaz separada aquí . Además, una discusión de cómo el principio se relaciona con lenguajes de tipo dinámico como JavaScript puede ser foudn aquí .

134
Derek Greer

Cuando diseñamos aplicaciones de software, podemos considerar las clases de bajo nivel, las clases que implementan operaciones básicas y primarias (acceso a discos, protocolos de red, ...) y las clases de alto nivel, las clases que encapsulan lógica compleja (flujos de negocios, ...).

Los últimos dependen de las clases de bajo nivel. Una forma natural de implementar tales estructuras sería escribir clases de bajo nivel y una vez que las tengamos para escribir las clases complejas de alto nivel. Dado que las clases de alto nivel se definen en términos de otros, esta parece ser la forma lógica de hacerlo. Pero este no es un diseño flexible. ¿Qué pasa si necesitamos reemplazar una clase de bajo nivel?

El Principio de Inversión de Dependencia establece que:

  • Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de las abstracciones.
  • Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.

Este principio busca "invertir" la noción convencional de que los módulos de alto nivel en software deberían depender de los módulos de nivel inferior. Aquí, los módulos de alto nivel son dueños de la abstracción (por ejemplo, decidiendo los métodos de la interfaz) que se implementan mediante módulos de nivel inferior. De este modo, los módulos de nivel inferior dependen de los módulos de nivel superior.

11
nikhil.singhal

La inversión de dependencia bien aplicada proporciona flexibilidad y estabilidad en el nivel de toda la arquitectura de su aplicación. Permitirá que su aplicación evolucione de forma más segura y estable.

Arquitectura tradicional en capas

Tradicionalmente, una IU de arquitectura en capas dependía de la capa de negocios y esto a su vez dependía de la capa de acceso a datos.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Tienes que entender la capa, el paquete o la biblioteca. Veamos cómo sería el código.

Tendríamos una biblioteca o paquete para la capa de acceso a datos.

// DataAccessLayer.dll
public class ProductDAO {

}

Y otra biblioteca o lógica empresarial de capa de paquete que depende de la capa de acceso a datos.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Arquitectura en capas con inversión de dependencia.

La inversión de dependencia indica lo siguiente:

Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de las abstracciones.

Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.

¿Qué son los módulos de alto nivel y bajo nivel? Pensando en módulos como bibliotecas o paquetes, los módulos de alto nivel serían aquellos que tradicionalmente tienen dependencias y un nivel bajo del que dependen.

En otras palabras, el nivel alto del módulo sería donde se invoca la acción y el nivel bajo donde se realiza la acción.

Una conclusión razonable a partir de este principio es que no debe haber dependencia entre las concreciones, sino que debe haber una dependencia en una abstracción. Pero de acuerdo con el enfoque que tomemos, podemos aplicar mal la inversión en función de la dependencia, pero una abstracción.

Imagina que adaptamos nuestro código de la siguiente manera:

Tendríamos una biblioteca o paquete para la capa de acceso a datos que define la abstracción.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

Y otra biblioteca o lógica empresarial de capa de paquete que depende de la capa de acceso a datos.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Aunque dependemos de una dependencia de abstracción entre el negocio y el acceso a los datos sigue siendo el mismo.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Para obtener la inversión de dependencia, la interfaz de persistencia debe definirse en el módulo o paquete donde se encuentra esta lógica o dominio de alto nivel y no en el módulo de bajo nivel.

Primero, defina qué es la capa de dominio y la abstracción de su comunicación es la persistencia definida.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Una vez que la capa de persistencia depende del dominio, puede invertir ahora si se define una dependencia.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

Profundizando el principio

Es importante asimilar bien el concepto, profundizando el propósito y los beneficios. Si nos mantenemos en forma mecánica y aprendemos el repositorio de casos típico, no podremos identificar dónde podemos aplicar el principio de dependencia.

Pero ¿por qué invertimos una dependencia? ¿Cuál es el objetivo principal más allá de ejemplos específicos?

Tal comúnmente permite que las cosas más estables, que no dependen de cosas menos estables, cambien con más frecuencia.

Es más fácil cambiar el tipo de persistencia, ya sea la base de datos o la tecnología para acceder a la misma base de datos que la lógica del dominio o las acciones diseñadas para comunicarse con la persistencia. Debido a esto, la dependencia se invierte porque es más fácil cambiar la persistencia si se produce este cambio. De esta forma no tendremos que cambiar el dominio. La capa de dominio es la más estable de todas, por lo que no debe depender de nada.

Pero no hay solo este ejemplo de repositorio. Hay muchos escenarios donde se aplica este principio y hay arquitecturas basadas en este principio.

Arquitecturas

Existen arquitecturas donde la inversión de dependencia es clave para su definición. En todos los dominios es el más importante y son las abstracciones las que indicarán el protocolo de comunicación entre el dominio y el resto de los paquetes o bibliotecas que están definidos.

Arquitectura limpia

En Arquitectura limpia el dominio está ubicado en el centro y si miras en la dirección de las flechas que indican la dependencia, está claro cuáles son las capas más importantes y estables. Las capas externas se consideran herramientas inestables, por lo tanto, evite depender de ellas.

Arquitectura hexagonal

Ocurre de la misma manera con la arquitectura hexagonal, donde el dominio también se encuentra en la parte central y los puertos son abstracciones de la comunicación desde el dominó hacia el exterior. Aquí nuevamente es evidente que el dominio es el más estable y la dependencia tradicional se invierte.

9
xurxodev

Para mí, el principio de inversión de dependencia, tal como se describe en el artículo oficial , es realmente un intento equivocado de aumentar la reutilización de módulos que son inherentemente menos reutilizables, así como una manera de solucionar un problema en C++ idioma.

El problema en C++ es que los archivos de encabezado suelen contener declaraciones de campos y métodos privados. Por lo tanto, si un módulo de C++ de alto nivel incluye el archivo de encabezado para un módulo de bajo nivel, dependerá de lo real implementación Detalles de ese módulo. Y eso, obviamente, no es algo bueno. Pero esto no es un problema en los lenguajes más modernos que se usan comúnmente en la actualidad.

Los módulos de alto nivel son inherentemente menos reutilizables que los módulos de bajo nivel porque los primeros son normalmente más específicos de la aplicación/contexto que los últimos. Por ejemplo, un componente que implementa una pantalla de UI es del nivel más alto y también muy (¿completamente?) Específico para la aplicación. Tratar de reutilizar un componente de este tipo en una aplicación diferente es contraproducente y solo puede conducir a un exceso de ingeniería.

Por lo tanto, la creación de una abstracción separada en el mismo nivel de un componente A que depende de un componente B (que no depende de A) se puede hacer solo si el componente A realmente será útil para reutilizarlo en diferentes aplicaciones o contextos. Si ese no es el caso, entonces aplicar DIP sería un mal diseño.

9
Rogério

Básicamente dice:

La clase debe depender de abstracciones (por ejemplo, interfaz, clases abstractas), no de detalles específicos (implementaciones).

7
martin.ra

Una forma mucho más clara de establecer el principio de inversión de dependencia es:

Los módulos que encapsulan la lógica empresarial compleja no deben depender directamente de otros módulos que encapsulan la lógica empresarial. En su lugar, deberían depender solo de interfaces a datos simples.

Es decir, en lugar de implementar su clase Logic como la gente usualmente hace:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

deberías hacer algo como:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Data y DataFromDependency deben vivir en el mismo módulo que Logic, no con Dependency.

¿Por qué hacer esto?

  1. Los dos módulos de lógica de negocios ahora están desacoplados. Cuando Dependency cambia, no necesita cambiar Logic.
  2. Comprender lo que hace Logic es una tarea mucho más simple: opera solo en lo que parece un ADT.
  3. Logic ahora se puede probar más fácilmente. Ahora puede instanciar directamente Data con datos falsos y pasarlos. No es necesario realizar simulacros o andamios complejos de prueba.
5
mattvonb

Aquí ya se dan buenas respuestas y buenos ejemplos.

La razón por la que DIP es importante es porque garantiza el principio de OO "diseño holgadamente acoplado".

Los objetos en su software NO deben entrar en una jerarquía donde algunos objetos son los de nivel superior, dependiendo de los objetos de bajo nivel. Los cambios en los objetos de bajo nivel se trasladarán a los objetos de nivel superior, lo que hace que el software sea muy frágil para el cambio.

Desea que sus objetos de 'nivel superior' sean muy estables y no frágiles para el cambio, por lo tanto, necesita invertir las dependencias.

5
Hace

Inversión de control (IoC) es un patrón de diseño en el que un objeto recibe su dependencia mediante un marco externo, en lugar de solicitar un marco para su dependencia.

Ejemplo de pseudocódigo usando búsqueda tradicional:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Código similar usando IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Los beneficios de IoC son:

  • No tiene dependencia en un marco central, por lo que puede cambiarlo si lo desea.
  • Dado que los objetos se crean por inyección, preferiblemente mediante interfaces, es fácil crear pruebas unitarias que reemplacen las dependencias con versiones simuladas.
  • Desacoplamiento del código.
3
Staale

El punto de inversión de la dependencia es hacer software reutilizable.

La idea es que, en lugar de dos piezas de código que se confían mutuamente, se basan en una interfaz abstracta. Entonces puedes reutilizar cualquier pieza sin la otra.

La forma en que esto se logra más comúnmente es a través de un contenedor de inversión de control (IoC) como Spring en Java. En este modelo, las propiedades de los objetos se configuran a través de una configuración XML en lugar de que los objetos salgan y encuentren su dependencia.

Imagina este pseudocódigo ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass depende directamente tanto de la clase de servicio como de la clase de ServiceLocator. Necesita ambos si desea utilizarlo en otra aplicación. Ahora imagina esto ...

public class MyClass
{
  public IService myService;
}

Ahora, MyClass se basa en una sola interfaz, la interfaz IService. Dejaríamos que el contenedor IoC realmente establezca el valor de esa variable.

Así que ahora, MyClass puede reutilizarse fácilmente en otros proyectos, sin traer consigo la dependencia de esas otras dos clases.

Aún mejor, no tienes que arrastrar las dependencias de MyService y las dependencias de esas dependencias, y ... bueno, entiendes la idea.

1
Marc Hughes

Creo que tengo mucho mejor ejemplo (más intuitivo).

  • Imagine un sistema (aplicación web) con gestión de empleados y contactos (dos pantallas).
  • No están exactamente relacionados, por lo que desea que cada uno esté en su propio módulo/carpeta.

Por lo tanto, tendría algún punto de entrada "principal" que tendría que conocer sobre el módulo de administración de Empleados y Módulo de administración de contactos, y tendría que proporcionar enlaces en la navegación y aceptar solicitudes de API, etc. En otras palabras , el módulo principal dependería de estas dos personas: sabría acerca de sus controladores, rutas y enlaces que se deben representar en la navegación (compartida).

Ejemplo de Node.js

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

Además, tenga en cuenta hay casos (simples) cuando esto está totalmente bien.


Por lo tanto, con el tiempo llegará a un punto en el que no es tan trivial agregar nuevos módulos. Debe recordar registrarse api, navegación, tal vez permisos, y este main.js se hace más y más grande.

Y ahí es donde entra la inversión de dependencia. En lugar de que su módulo principal dependa de todos los demás módulos, introducirá un poco de "núcleo" y hará que cada módulo se registre.

Entonces, en este caso se trata de tener una idea de algún ApplicationModule, que puede enviarse a muchos servicios (rutas, navegación, permisos) y el módulo principal puede seguir siendo simple (solo importe el módulo y déjelo instalar)

En otras palabras, se trata de hacer arquitectura enchufable. Esto es un trabajo y un código adicionales que tendrá que escribir/leer y mantener, por lo que no debe hacerlo por adelantado, sino cuando tiene este tipo de olor.

Lo que es especialmente interesante es que puede convertir cualquier cosa en un complemento, incluso en la capa de persistencia, lo que podría valer la pena hacer si necesita admitir muchas implementaciones de persistencia, pero ese no suele ser el caso. Vea la otra respuesta para la imagen con arquitectura hexagonal, es genial para la ilustración: hay un núcleo y todo lo demás es esencialmente un complemento.

0
Kamil Tomšík

Inversión de dependencia: Depende de abstracciones, no de concreciones.

Inversión de control: Principal contra Abstracción, y cómo el Principal es el pegamento de los sistemas.

DIP and IoC

Estas son algunas buenas publicaciones que hablan de esto:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

Inversión de los contenedores de control y el patrón de inyección de dependencia por Martin Fowler también es una buena lectura. Encontré Head First Design Patterns un libro increíble para mi primera incursión en el aprendizaje de DI y otros patrones.

0
Chris Canal

Además de otras respuestas ....

Permítanme presentarles un ejemplo primero.

Que haya un hotel que le pida a un generador de alimentos sus suministros. El hotel le da el nombre de la comida (por ejemplo, pollo) al generador de alimentos y el generador devuelve la comida solicitada al hotel. Pero el hotel no se preocupa por el tipo de comida que recibe y sirve. Así que el generador suministra los alimentos con una etiqueta de "Alimentos" al hotel.

Esta implementación está en Java

FactoryClass con un método de fábrica. El generador de alimentos

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


Una clase de interfaz/resumen

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


El pollo implementa la comida (una clase concreta)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


Pescado implementa el alimento (una clase concreta)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


Finalmente

El hotel

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

Como puede ver, el hotel no sabe si es un objeto de pollo o un objeto de pescado. Solo sabe que es un objeto de comida, es decir, un hotel depende de la clase de comida.

También notaría que la clase de pescado y pollo implementa la clase de alimentos y no está relacionada directamente con el hotel. es decir, pollo y pescado también depende de la clase de alimentos.

Lo que implica que tanto el Componente de nivel alto (Hotel) como el Componente de nivel bajo (Pescado y pollo) dependen de una abstracción (Comida).

Esto se llama inversión de dependencia.

0
Revolver

El principio de inversión de dependencia (DIP) dice que

i) Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de las abstracciones.

ii) Las abstracciones nunca deben depender de los detalles. Los detalles deben depender de las abstracciones.

Ejemplo:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Nota: La clase debe depender de abstracciones como interfaz o clases abstractas, no detalles específicos (implementación de interfaz).

0
Rejwanul Reja