Inyección de dependencia - Dependency injection

En ingeniería de software , la inyección de dependencias es una técnica en la que un objeto recibe otros objetos de los que depende, llamados dependencias. Normalmente, el objeto receptor se llama cliente y el objeto pasado ('inyectado') se llama servicio . El código que pasa el servicio al cliente se llama inyector. En lugar de que el cliente especifique qué servicio utilizará, el inyector le dice al cliente qué servicio utilizar. La 'inyección' se refiere al paso de una dependencia (un servicio) al cliente que lo usa.

El servicio pasa a formar parte del estado del cliente . Pasar el servicio al cliente, en lugar de permitir que un cliente cree o encuentre el servicio , es el requisito fundamental del patrón.

La intención detrás de la inyección de dependencia es lograr la separación de preocupaciones de construcción y uso de objetos. Esto puede aumentar la legibilidad y la reutilización del código.

La inyección de dependencia es una forma de la técnica más amplia de inversión de control . Un cliente que quiera llamar a algunos servicios no debería tener que saber cómo construir esos servicios. En cambio, el cliente delega en un código externo (el inyector). El cliente no conoce el inyector. El inyector pasa los servicios, que pueden existir o ser construidos por el propio inyector, al cliente. Luego, el cliente utiliza los servicios.

Esto significa que el cliente no necesita saber sobre el inyector, cómo construir los servicios o incluso qué servicios está utilizando realmente. El cliente solo necesita conocer las interfaces de los servicios, porque estas definen cómo el cliente puede utilizar los servicios. Esto separa la responsabilidad de "uso" de la responsabilidad de "construcción".

Visión general

Inyección de dependencia para niños de cinco años

Cuando vas y sacas cosas del refrigerador por ti mismo, puedes causar problemas. Puede dejar la puerta abierta, puede conseguir algo que mamá o papá no quieren que usted tenga. Incluso podría estar buscando algo que ni siquiera tenemos o que ha caducado.

Lo que debería hacer es indicar una necesidad, "Necesito algo para beber con el almuerzo", y luego nos aseguraremos de que tenga algo cuando se siente a comer.

John Munsch, 28 de octubre de 2009.

La inyección de dependencia resuelve los siguientes problemas:

  • ¿Cómo puede una clase ser independiente de cómo se crean los objetos de los que depende?
  • ¿Cómo se puede especificar la forma en que se crean los objetos en archivos de configuración separados?
  • ¿Cómo puede una aplicación admitir diferentes configuraciones?

La creación de objetos directamente dentro de la clase compromete a la clase a implementaciones particulares. Esto hace que sea difícil cambiar la instanciación en tiempo de ejecución, especialmente en lenguajes compilados donde cambiar los objetos subyacentes puede requerir volver a compilar el código fuente.

La inyección de dependencia separa la creación de dependencias de un cliente del comportamiento del cliente, lo que promueve programas poco acoplados y los principios de inversión de dependencia y responsabilidad única . Básicamente, la inyección de dependencias se basa en pasar parámetros a un método .

La inyección de dependencia es un ejemplo del concepto más general de inversión de control .

Un ejemplo de inversión de control sin inyección de dependencia es el patrón del método de plantilla , donde el polimorfismo se logra mediante subclases . La inyección de dependencia implementa la inversión del control a través de la composición y, a menudo, es similar al patrón de estrategia . Si bien el patrón de estrategia está destinado a dependencias que son intercambiables a lo largo de la vida útil de un objeto , en la inyección de dependencia puede ser que se utilice solo una instancia única de una dependencia. Esto todavía logra el polimorfismo, pero a través de la delegación y la composición .

La inyección de dependencias contrasta directamente con el patrón del localizador de servicios , que permite a los clientes conocer el sistema que utilizan para encontrar dependencias.

Roles

La inyección de dependencia involucra cuatro roles:

  • los objetos de servicio que se utilizarán
  • el objeto cliente , cuyo comportamiento depende de los servicios que utiliza
  • las interfaces que definen cómo el cliente puede utilizar los servicios
  • el inyector , que construye los servicios y los inyecta en el cliente

Como analogía,

  • servicio: un automóvil eléctrico, de gasolina, híbrido o diésel
  • cliente: un conductor que usa el automóvil de la misma manera independientemente del motor
  • Interfaz: transmisión automática , que garantiza que el conductor no tenga que comprender los detalles de los cambios de marcha.
  • inyector: el padre que compró el automóvil para el conductor y decidió de qué tipo

Cualquier objeto que pueda utilizarse puede considerarse un servicio . Cualquier objeto que utilice otros objetos puede considerarse un cliente . Los nombres se relacionan solo con el papel que juegan los objetos en una inyección.

Las interfaces son los tipos que el cliente espera que sean sus dependencias. El cliente no debe conocer la implementación específica de sus dependencias, solo conocer el nombre de la interfaz y la API . Como resultado, el cliente no necesitará cambiar incluso si cambia lo que está detrás de la interfaz. La inyección de dependencia puede funcionar con interfaces verdaderas o clases abstractas, pero también con servicios concretos , aunque esto violaría el principio de inversión de dependencia y sacrificaría el desacoplamiento dinámico que permite las pruebas . Solo se requiere que el cliente nunca trate sus interfaces como concretas construyéndolas o extendiéndolas. Si la interfaz se refactoriza de una clase a un tipo de interfaz (o viceversa), será necesario volver a compilar el cliente. Esto es significativo si el cliente y los servicios se publican por separado.

El inyector presenta servicios al cliente. A menudo, también construye al cliente. Un inyector puede conectar un gráfico de objeto complejo tratando el mismo objeto como cliente en un punto y como servicio en otro. El inyector en sí puede ser en realidad muchos objetos trabajando juntos, pero puede que no sea el cliente (ya que esto crearía una dependencia circular ). El inyector puede denominarse ensamblador, proveedor, contenedor, fábrica, constructor, resorte o código de construcción.

Como disciplina, la inyección de dependencia pide que todos los objetos separen la construcción y el comportamiento. Depender de un marco DI para la construcción puede llevar a prohibir la newpalabra clave o, de manera menos estricta, a permitir solo la construcción directa de objetos de valor .

Estructura

Un ejemplo de diagrama de secuencia y clase UML para el patrón de diseño de inyección de dependencia.

En el diagrama de clases de UML anterior , la Clientclase requiere ServiceAy ServiceB, pero no crea una instancia de los objetos ServiceA1y ServiceB1directamente.

En cambio, una Injectorclase crea los objetos y los inyecta en el Client. El Clientes independiente de qué clases se instancian.
El diagrama de secuencia UML de la derecha muestra las interacciones en tiempo de ejecución:

  1. El Injectorobjeto crea los objetos ServiceA1y ServiceB1.
  2. El Injectorcrea el Clientobjeto.
  3. El Injectorinyecta los objetos ServiceA1y ServiceB1en el Clientobjeto.

Ventajas y desventajas

Ventajas

Un beneficio básico de la inyección de dependencias es un menor acoplamiento entre clases y sus dependencias. Al eliminar el conocimiento de un cliente de cómo se implementan sus dependencias, los programas se vuelven más reutilizables, probables y fáciles de mantener.

Esto también da como resultado una mayor flexibilidad: un cliente puede actuar sobre cualquier cosa que admita la interfaz intrínseca que espera el cliente.

Muchos de los beneficios de la inyección de dependencia son particularmente relevantes para las pruebas unitarias .

Por ejemplo, la inyección de dependencia se puede utilizar para externalizar los detalles de configuración de un sistema en archivos de configuración, lo que permite que el sistema se reconfigure sin volver a compilarlo. Se pueden escribir configuraciones separadas para diferentes situaciones que requieren diferentes implementaciones de componentes. Esto incluye pruebas. De manera similar, debido a que la inyección de dependencias no requiere ningún cambio en el comportamiento del código, se puede aplicar al código heredado como refactorización . El resultado son clientes que son más independientes y que son más fáciles de probar unitariamente de forma aislada utilizando stubs u objetos simulados que simulan otros objetos que no están bajo prueba. Esta facilidad de prueba es a menudo el primer beneficio que se nota cuando se usa la inyección de dependencia.

De manera más general, la inyección de dependencias reduce el código repetitivo , ya que toda la creación de dependencias es manejada por un componente singular.

Finalmente, la inyección de dependencia permite el desarrollo concurrente. Dos desarrolladores pueden desarrollar clases de forma independiente que se utilicen entre sí, mientras que solo necesitan conocer la interfaz a través de la cual se comunicarán las clases. Los complementos a menudo son desarrollados por tiendas de terceros que ni siquiera hablan con los desarrolladores que crearon el producto que utiliza los complementos.

Desventajas

Las críticas a la inyección de dependencia argumentan que:

  • Crea clientes que exigen detalles de configuración, que pueden ser onerosos cuando hay disponibles valores predeterminados obvios.
  • Haga que el código sea difícil de rastrear porque separa el comportamiento de la construcción.
  • Suele implementarse con reflexión o programación dinámica. Esto puede dificultar la automatización de IDE .
  • Por lo general, requiere un mayor esfuerzo de desarrollo inicial.
  • Fuerza la complejidad fuera de las clases y en los vínculos entre clases que pueden ser más difíciles de administrar.
  • Fomentar la dependencia de un marco.

Tipos de inyección de dependencia

Hay al menos tres formas en que un cliente puede recibir una referencia a un módulo externo:

  • Inyección de constructor: las dependencias se proporcionan a través del constructor de clases de un cliente.
  • Inyección de setter: el cliente expone un método de setter que el inyector usa para inyectar la dependencia.
  • Inyección de interfaz: la interfaz de la dependencia proporciona un método de inyección que inyectará la dependencia en cualquier cliente que se le pase.

Los marcos de DI y los marcos de prueba pueden usar otros tipos de inyección.

Algunos marcos de prueba modernos ni siquiera requieren que los clientes acepten activamente la inyección de dependencias, lo que hace que el código heredado sea comprobable. En Java , la reflexión puede hacer públicos los atributos privados al realizar pruebas y, por lo tanto, aceptar inyecciones por asignación.

Algunos intentos de inversión de control simplemente sustituyen una forma de dependencia por otra. Como regla general, si un programador no puede mirar nada más que el código del cliente y decir qué marco se está utilizando, entonces el cliente tiene una dependencia codificada en el marco.

Sin inyección de dependencia

En el siguiente ejemplo de Java , la Clientclase contiene una Service variable miembro inicializada en el constructor . El cliente controla qué servicio se utiliza y su construcción. El cliente tiene una dependencia codificada de forma rígida en ExampleService.

public class Client {
    // Internal reference to the service used by this client
    private ExampleService service;

    // Constructor
    Client() {
        // Specify a specific implementation instead of using dependency injection
        service = new ExampleService();
    }

    // Method that uses the services
    public String greet() {
        return "Hello " + service.getName();
    }
}

Podemos ajustar este ejemplo utilizando las técnicas que se describen a continuación.

Inyección de constructor

Este método requiere que el cliente proporcione un parámetro en un constructor para la dependencia. Esto asegura que el objeto del cliente esté siempre en un estado válido, en lugar de que algunas de sus dependencias sean nulas o no se establezcan. Este puede ser un primer paso para hacer que el cliente sea inmutable y, por lo tanto, seguro para subprocesos . Sin embargo, por sí solo, este patrón carece de la flexibilidad para cambiar sus dependencias más adelante.

// Constructor
Client(Service service, Service otherService) {
    if (service == null) {
        throw new InvalidParameterException("service must not be null");
    }
    if (otherService == null) {
        throw new InvalidParameterException("otherService must not be null");
    }

    // Save the service references inside this client
    this.service = service;
    this.otherService = otherService;
}

Inyección de setter

Este método requiere que el cliente proporcione un método de establecimiento para la dependencia. Los inyectores pueden manipular las referencias de dependencia en cualquier momento.

Esto ofrece flexibilidad, pero es difícil garantizar que todas las dependencias se inyecten antes de que se utilice el cliente. Debido a que estas inyecciones ocurren de forma independiente, una dependencia puede dejarse nula simplemente porque el inyector no llama a su establecedor. Por el contrario, una dependencia declarada en un constructor significa que no se puede construir un objeto a menos que se satisfaga la dependencia.

El siguiente ejemplo muestra una forma para que un cliente verifique que la inyección se completó cuando se usó.

// Set the service to be used by this client
public void setService(Service service) {
    this.service = service;
}

// Set the other service to be used by this client
public void setOtherService(Service otherService) {
    this.otherService = otherService;
}

// Check the service references of this client
private void validateState() {
    if (service == null) {
        throw new IllegalStateException("service must not be null");
    }
    if (otherService == null) {
        throw new IllegalStateException("otherService must not be null");
    }
}

// Method that uses the service references
public void doSomething() {
    validateState();
    service.doYourThing();
    otherService.doYourThing();
}

Inyección de interfaz

Con la inyección de interfaz, las dependencias pueden ignorar por completo a sus clientes, y aun así enviar y recibir referencias a nuevos clientes.

De esta forma, las dependencias se convierten en inyectores. La clave es que el método de inyección (que podría ser simplemente un método de establecimiento) se proporciona a través de una interfaz.

Todavía se necesita un ensamblador para presentar el cliente y sus dependencias. El ensamblador toma una referencia al cliente, la envía a la interfaz de establecimiento que establece esa dependencia y la pasa a ese objeto de dependencia que, a su vez, pasa una referencia a sí mismo al cliente.

Para que la inyección de interfaz tenga valor, la dependencia debe hacer algo además de simplemente devolverse una referencia a sí misma. Esto podría actuar como una fábrica o un sub-ensamblador para resolver otras dependencias, abstrayendo así algunos detalles del ensamblador principal. Podría ser un recuento de referencias para que la dependencia sepa cuántos clientes lo están usando. Si la dependencia mantiene una colección de clientes, luego podría inyectarlos a todos con una instancia diferente de sí misma.

// Service setter interface.
public interface ServiceSetter {
    public void setService(Service service);
}

// Client class
public class Client implements ServiceSetter {
    // Internal reference to the service used by this client.
    private Service service;

    // Set the service that this client is to use.
    @Override
    public void setService(Service service) {
        this.service = service;
    }
}

// Injector class
public class ServiceInjector {
	Set<ServiceSetter> clients;
	public void inject(ServiceSetter client) {
		clients.add(client);
		client.setService(new ServiceFoo());
	}
	public void switchToBar() {
		for (Client client : clients) {
			client.setService(new ServiceBar());
		}
	}
}

// Service classes
public class ServiceFoo implements Service {}
public class ServiceBar implements Service {}

Montaje

La forma más sencilla de implementar la inyección de dependencias es organizar manualmente los servicios y los clientes, normalmente en la "raíz" del programa, donde comienza la ejecución.

public class Injector {
    public static void main(String[] args) {
        // Build the dependencies first
        Service service = new ExampleService();

        // Inject the service, constructor style
        Client client = new Client(service);

        // Use the objects
        System.out.println(client.greet());
    }	
}

El ejemplo anterior construye el gráfico de objetos manualmente y luego lo invoca. Este inyector no es 'puro', ya que usa uno de los objetos que construye ( Client).

La construcción manual puede ser más compleja e involucrar a constructores , fábricas u otros patrones de construcción .

Frameworks

Un diagrama de clases de contenedores de inyección de dependencia en .NET Framework.
Los contenedores como Ninject o Structure map se usan comúnmente en lenguajes de programación orientados a objetos para lograr la inversión del control .

La inyección manual de dependencias se convierte en un marco de inyección de dependencias una vez que el código de construcción ya no está personalizado para la aplicación y, en cambio, es universal.

Los marcos de aplicación como Weld , Spring , Guice , Play framework , Salta , Glassfish HK2 , Dagger y Managed Extensibility Framework admiten la inyección de dependencia, pero no es necesario que lo haga.

Los marcos como Spring pueden construir objetos y conectarlos antes de devolver una referencia al cliente. Debido a que los marcos como Spring permiten que los detalles del ensamblaje se externalicen en los archivos de configuración, todas las menciones del concreto ExampleServicese pueden mover del código a los datos de configuración:

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Injector {
	public static void main(String[] args) {
		// -- Assembling objects -- //
		BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
		Client client = (Client) beanfactory.getBean("client");

		// -- Using objects -- //
		System.out.println(client.greet());
	}
}

Incluso con un gráfico de objetos largo y complejo, la única clase mencionada en el código es el punto de entrada, en este caso Client.

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="service" class="ExampleService">
    </bean>

    <bean id="client" class="Client">
        <constructor-arg value="service" />        
    </bean>
</beans>

Clienty Serviceno han sufrido ningún cambio para trabajar con Spring y siguen siendo POJO . Spring puede conectar servicios y clientes que desconocen por completo su existencia. Al evitar que las anotaciones y llamadas específicas de Spring se extiendan entre muchas clases, el sistema depende solo ligeramente de Spring.

Mantener los POJO puros requiere esfuerzo. En lugar de mantener archivos de configuración complejos, las clases pueden usar anotaciones para permitir que Spring haga el trabajo duro usando la convención sobre la configuración .

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Injector {
	public static void main(String[] args) {
		// Assemble the objects
		BeanFactory beanfactory = new AnnotationConfigApplicationContext(MyConfiguration.class);
		Client client = beanfactory.getBean(Client.class);

		// Use the objects
		System.out.println(client.greet());
	}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan
public class MyConfiguration {
    @Bean
    public Client client(ExampleService service) {
        return new Client(service);
    }
}
@Component
public class ExampleService {
    public String getName() {
        return "World!";
    }
}

Los diferentes inyectores (fábricas, localizadores de servicios y contenedores de inyección de dependencia) se diferencian principalmente en el lugar donde se pueden usar. Mover llamadas a una fábrica o un localizador de servicios fuera del cliente y dentro de la raíz del programa es equivalente a usar un contenedor de inyección de dependencia.

Al eliminar el conocimiento del inyector, el cliente se libera del conocimiento del mundo exterior, se queda atrás. Sin embargo, cualquier objeto que utilice otros objetos puede considerarse un cliente, incluido el objeto que contiene la raíz del programa. Como resultado, mainen el ejemplo anterior se está utilizando realmente el patrón de localizador de servicios. Esto no se puede evitar porque la elección de las implementaciones del servicio debe realizarse en algún lugar.

Ejemplos de

AngularJS

En AngularJS , solo hay tres formas en que un componente (objeto o función) puede acceder directamente a sus dependencias:

  1. El componente puede crear la dependencia, normalmente utilizando el newoperador.
  2. El componente puede buscar la dependencia, haciendo referencia a una variable global.
  3. Se puede pasar la dependencia al componente donde sea necesario.

Las dos primeras opciones no son óptimas porque codifican de forma rígida la dependencia del componente. Esto es problemático en las pruebas , donde a menudo es deseable simular dependencias para el aislamiento de la prueba.

La tercera opción elimina la responsabilidad de localizar la dependencia del componente.

function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
}

SomeClasssimplemente se entrega al recibidor cuando se crea una instancia. Esto le da la responsabilidad de la construcción de dependencias al código que construye SomeClass.

Para gestionar esto, cada aplicación AngularJS tiene un 'inyector': un localizador de servicios responsable de la construcción y búsqueda de dependencias.

// Provide the wiring information in a module
var myModule = angular.module('myModule', []);

// Teach the injector how to build a greeter service. 
// greeter is dependent on the $window service. 
// The greeter service is an object that
// contains a greet method.
myModule.factory('greeter', function($window) {
  return {
    greet: function(text) {
      $window.alert(text);
    }
  };
});

Cree un nuevo inyector que pueda proporcionar componentes definidos en el myModulemódulo y solicite nuestro servicio de bienvenida al inyector.

var injector = angular.injector(['myModule', 'ng']);
var greeter = injector.get('greeter');

Solicitar dependencias resuelve el problema de la codificación rígida, pero también significa que el inyector debe pasarse por toda la aplicación. Pasar el inyector infringe la Ley de Deméter . Para remediar esto, usamos una notación declarativa en nuestras plantillas HTML, para pasar la responsabilidad de crear componentes al inyector:

<div ng-controller="MyController">
  <button ng-click="sayHello()">Hello</button>
</div>
function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}

Cuando AngularJS compila el HTML, procesa la ng-controllerdirectiva, que a su vez le pide al inyector que cree una instancia del controlador y sus dependencias.

injector.instantiate(MyController);

Debido a que ng-controllerdifiere que el inyector cree una instancia de la clase, puede satisfacer las dependencias de MyControllersin que el controlador sepa sobre el inyector. El código de la aplicación simplemente declara las dependencias que necesita, preservando la Ley de Demeter .

C#

Ejemplo de inyección de constructor , inyección de Setter e inyección de interfaz en C #

using System;

namespace DependencyInjection
{
    // An interface for the library
    interface IGamepadFunctionality
    {
        String GetGamepadName();
        void SetVibrationPower(float InPower);
    }

    // Concrete implementation of the xbox controller functionality
    class XBoxGamepad : IGamepadFunctionality
    {
        readonly String GamepadName = "XBox Controller";
        float VibrationPower = 1.0f;
        public String GetGamepadName() => GamepadName;
        public void SetVibrationPower(float InPower) => VibrationPower = Math.Clamp(InPower, 0.0f, 1.0f);

    }

    // Concrete implementation of the playstation controller functionality
    class PlaystationJoystick : IGamepadFunctionality
    {
        readonly String ControllerName = "Playstation controller";
        float VibratingPower = 100.0f;
        public String GetGamepadName() => ControllerName;
        public void SetVibrationPower(float InPower) => VibratingPower = Math.Clamp(InPower * 100.0f, 0.0f, 100.0f);
    }

    // Concrete implementation of the steam controller functionality
    class SteamController : IGamepadFunctionality
    {
        readonly String JoystickName = "Steam controller";
        double Vibrating = 1.0;
        public String GetGamepadName() => JoystickName;
        public void SetVibrationPower(float InPower) => Vibrating = Convert.ToDouble(Math.Clamp(InPower, 0.0f, 1.0f));
    }

    // An interface for gamepad functionality injections
    interface IGamepadFunctionalityInjector
    {
        void InjectFunctionality(IGamepadFunctionality InGamepadFunctionality);
    }

    class CGamepad : IGamepadFunctionalityInjector
    {
        IGamepadFunctionality _GamepadFunctionality;

        public CGamepad()
        {

        }
        // Constructor injection
        public CGamepad(IGamepadFunctionality InGamepadFunctionality) => _GamepadFunctionality = InGamepadFunctionality;

        // Setter injection
        public void SetGamepadFunctionality(IGamepadFunctionality InGamepadFunctionality) => _GamepadFunctionality = InGamepadFunctionality;

        // Interface injection
        public void InjectFunctionality(IGamepadFunctionality InGamepadFunctionality) => _GamepadFunctionality = InGamepadFunctionality;

        public void Showcase()
        {
            String Message = String.Format("We're using the {0} right now, do you want to change the vibrating power?\r\n", _GamepadFunctionality.GetGamepadName());
            Console.WriteLine(Message);
        }
    }

    enum EPlatforms: byte
    {
        Xbox,
        Playstation,
        Steam
    }

    class CGameEngine
    {
        EPlatforms _Platform;
        CGamepad _Gamepad;
        public void SetPlatform(EPlatforms InPlatform)
        {
            _Platform = InPlatform;
            switch(_Platform)
            {
                case EPlatforms.Xbox:

                    // injects dependency on XBoxGamepad class through Constructor Injection
                    _Gamepad = new CGamepad(new XBoxGamepad());
                    break;
                case EPlatforms.Playstation:
                    _Gamepad = new CGamepad();

                    // injects dependency on PlaystationJoystick class through Setter Injection
                    _Gamepad.SetGamepadFunctionality(new PlaystationJoystick());
                    break;
                case EPlatforms.Steam:
                    _Gamepad = new CGamepad();

                    // injects dependency on SteamController class through Interface Injection
                    _Gamepad.InjectFunctionality(new SteamController());
                    break;
            }

            _Gamepad.Showcase();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            
            CGameEngine Engine = new CGameEngine();

            Engine.SetPlatform(EPlatforms.Steam);

            Engine.SetPlatform(EPlatforms.Xbox);

            Engine.SetPlatform(EPlatforms.Playstation);
        }
    }
}

Ver también

Referencias

enlaces externos