Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Analisist 1
Analisist 1
Object Pool (OP) es un patrón creacional, integrante los “gang of four”. Este
consiste en una clase, que contiene dentro de ella una colección de objetos
previamente instanciados, listos para ser utilizados. El OP, es quien tiene la
mayoría de responsabilidades en cuanto al manejo de los objetos se refiere.
Cuando una clase solicita un objeto, la clase OP se encarga de retornar este
objeto, y moverlo a una estructura de datos, generalmente una lista, de objetos
que están en uso en este momento. Dependiendo de la implementación y el
contexto del OP, este puede llegar a quedarse sin objetos para retornar, en este
caso, OP se encarga de instanciar un nuevo objeto, agregarlo a la lista de
objetos disponibles, y retornarlo. Además, para evitar que la clase sea
innecesariamente grande, OP puede tener un atributo que indique la cantidad
máxima de objetos que puede contener; si la cantidad llega a este límite y un
objeto es solicitado, se debe esperar a que algún objeto vuelva a estar disponible.
El OP, suele instanciarse como singleton. De esta manera, hay un mejor manejo
de los objetos, ya que estos volverían siempre a la misma clase OP de donde
salieron.
Propuesta UML
Ejemplo
Contexto
Aplicación patrón en lenguaje y SOLID
namespace DesignPattern.Objectpool
{
// The PooledObject class is the type that is expensive or slow to instantiate,
// or that has limited availability, so is to be held in the object pool.
public class PooledObject
{
DateTime _createdAt = DateTime.Now;
// The Pool class is the most important class in the object pool design pattern. It
controls access to the
// pooled objects, maintaining a list of available objects and a collection of objects
that have already been
// requested from the pool and are still in use. The pool also ensures that objects
that have been released
// are returned to a suitable state, ready for the next time they are requested.
public static class Pool
{
private static List<PooledObject> _available = new List<PooledObject>();
private static List<PooledObject> _inUse = new List<PooledObject>();
lock (_available)
{
_available.Add(po);
_inUse.Remove(po);
}
}
Singleton
El patrón singleton consiste en crear una instancia de un objeto y solo una, para
toda nuestra aplicación. Sería como una especie de variable global que almacena
nuestro objeto.
En un primer momento esta definición puede sonar muy extraña. Por lo general,
siempre se recomienda no usar variables globales en una aplicación, y mucho
menos en programación orientada a objetos. Pero cuando hablamos de singleton,
estamos jugando a crear una especie de variable global de forma encubierta.
¿Cómo puede ser esto un patrón?
private Singleton()
{
}
En esta pequeña porción de código hemos conseguido realizar una única instancia
en el momento en el que se llama por primera vez. Además hemos creado un
constructor con acceso privado para que nadie pueda instanciar la clase. Y para
terminar hemos creado una propiedad de solo lectura con la que se puede
acceder a la instancia creada. Pero ésta no será Thread-safe. Para conseguirlo
podríamos modificar la clase de la siguiente forma:
private Singleton() { }
private Singleton() { }
return instance;
}
}
}
private Singleton() { }
private Logger() { }
Viendo este código en nuestra aplicación, está claro que para poder escribir en el
log desde cualquier punto de la misma sólo tendremos que hacer esta llamada:
Logger.Current.WriteInformation("Una información");
Logger.Current.WriteWarning("Un aviso");
Logger.Current.WriteError("Un error");
Al pararnos a pensar las consecuencias de escribir este código, caeremos en la
cuenta de que singleton nos está creando una dependencia en todo el programa
donde queramos tener información del proceso en forma de logs (eso es a lo largo
de toda la aplicación). Algo que comunmente conocemos como acoplamiento
entre clases.
De esta forma, delegaríamos la gestión del ciclo de vida de las instancias al IoC
Container que hayamos decidido. A continuación mostraremos cómo podemos
configurar una instancia singleton usando las frameworks de inyección de
dependencias (DI) más conocidas:
// configurar
ObjectFactory.Initialize(x =>
{
x.For<ILogger>().Singleton().Use<Logger>();
}
// recoger valor
var x = ObjectFactory.GetInstance<ILogger>();
Con Ninject:
// configurar
IKernel ninject = new StandardKernel(new InlineModule(
x => x.Bind<ILogger>().To<Logger>(),
x => x.Bind<Logger>().ToSelf().InSingletonScope()));
// recoger valor
var x = ninject.Get<ILogger>();
Con Unity:
// configurar
IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, Logger>(new
ContainerControlledLifetimeManager());
// recoger valor
var x = container.Resolve<ILogger>();
O con autofact:
private Logger() { }
public Logger()
{
// ...
}
public void WriteInformation(string message)
{
// ...
}
public void WriteWarning(string message)
{
// ...
}
public void WriteError(string message)
{
// ...
}
}
Con este código sería nuestro singleton Logger quien gestione el ciclo de vida y
conseguiríamos desacoplarnos de la implementación gracias al IoC.
ObjectFactory.Initialize(x =>
{
x.For<ILogger>().Use(Logger.Current);
}
var x = ObjectFactory.GetInstance<ILogger>();
var x = ninject.Get<ILogger>();
El patrón Composite en la práctica
Los patrones de diseño del Gang of Four son una de las piedras angulares en el
desarrollo de software. Algunos de ellos tan importantes que han sido absorbidos
por implementaciones nativas en los lenguajes de programación y los damos
como algo dado sin darnos cuenta del patrón que reside detrás (caso de Iterator).
Composite
Este patrón es, a mi parecer, uno de los más útiles para conseguir un código
dividido en pequeñas piezas reusables y testeables, que respetan al máximo el
Single Responsibility Principle. Esto significa que una vez tenemos desarrollada
cierta funcionalidad dentro de una clase, en pocas ocasiones tendremos un motivo
para cambiar dicha clase en el futuro. Lo veremos más claro con el ejemplo.
Es este último punto precisamente el que supone un pequeño hándicap para este
patrón de diseño. En el libro de la serie “Head First” se discute bastante sobre este
asunto, el problema es que estas operaciones al ser específicas de la clase
Composite no pertenecen a la interfaz, por lo que, en cierta medida, los clientes
han de ser conscientes de las implementaciones que existen de la interfaz (y
hacer downcasting), rompiendo el principio Program to an interface, not an
implementation. Otra posible solución sería elevar las operaciones a la interfaz, y
lanzando una excepción si las invocamos desde Leaf, pero esa solución me
parece igualmente mala.
La versión inmutable
Sin embargo, el mayor uso que yo he ido encontrando para este patrón es
bastante diferente, y guarda relación con lo que mencionaba al principio de este
artículo. Cuando nos enfrentamos al desarrollo de aplicaciones de gran dimensión,
es muy frecuente que determinadas clases que tienen como objetivo realizar
tareas repetitivas en pasos sucesivos vayan creciendo y creciendo debido a
nuevos requerimientos.
En gran medida, estos problemas pueden evitarse con lo que yo llamo la versión
inmutable del patrón Composite. Es inmutable porque se inicializa una sola vez y
no permite modificar su estrucutra, es decir, nos cargamos las operaciones add y
remove de la versión vista más arriba. Veámoslo con un ejemplo concreto.
La clase CompositeValidator
@Override
public List<String> validate(T info) {
List<String> errors = Lists.newArrayList();
return errors;
}
}
Como vemos, no hace más que delegar la validación a cada uno de sus “hijos” y
recopilar los resultados. Lo bueno de esta estructura, es que ¡los hijos pueden ser
tanto implementaciones concretas como nuevos Composites!
@Override
public List<String> validate(NewUserInfo info) {
List<String> errors = Lists.newArrayList();
if (StringUtils.isEmpty(name)) {
errors.add("Name must be populated");
}
if (StringUtils.isEmpty(surname)) {
errors.add("Surname must be populated");
}
return errors;
}
}
public class AgeValidator implements Validator<NewUserInfo> {
@Override
public List<String> validate(NewUserInfo info) {
List<String> errors = Lists.newArrayList();
return errors;
}
}
Creo que queda bastante claro de qué forma se simplifica el código en pequeñas
piezas reutilizables. Un posible temor ante esta situación es encontrarnos ante un
montón de clases por mantener, pero yo diría que es mucho más fácil mantener
pequeñas clases fáciles de entender y localizar que una clase gigantesca en la
que hay que bucear para encontrar el código a modificar. Y por último reducimos
los potenciales motivos para modificar una clase determinada.
<bean id="newUserInfoValidator"
class="com.raulavila.patterns.composite.CompositeValidator">
<constructor-arg>
<list>
<ref bean="nameAndAddressValidator" />
<ref bean="ageValidator" />
<ref bean="passwordValidator" />
</list>
</constructor-arg>
</bean>
<bean id="nameAndAddressValidator"
class="com.raulavila.patterns.composite.CompositeValidator">
<constructor-arg>
<list>
<ref bean="nameValidator" />
<ref bean="addressValidator" />
</list>
</constructor-arg>
</bean>
<bean id="nameValidator"
class="com.raulavila.patterns.composite.NameValidator"/>
<bean id="addressValidator"
class="com.raulavila.patterns.composite.AddressValidator"/>
<bean id="ageValidator"
class="com.raulavila.patterns.composite.AgeValidator"/>
<bean id="passwordValidator"
class="com.raulavila.patterns.composite.PasswordValidator"/>
@SuppressWarnings("unchecked")
Validator<NewUserInfo> newInfoUserValidator =
context.getBean("newUserInfoValidator", Validator.class);
System.out.println(errors);
La salida de esta pequeña aplicación, dado que estamos validando una instancia
sin rellenar (por lo que contiene los valores por defecto) sería una lista con los
siguientes mensajes:
Veamos por último lo sencillos que quedarían algunos de los tests. Empecemos
con el test de la clase CompositeValidator:
@Mock
private Validator<Object> validator1;
@Mock
private Validator<Object> validator2;
@Mock
private Object info;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
List<Validator<Object>> validators =
Lists.newArrayList(validator1, validator2);
when(validator1.validate(info)).thenReturn(Lists.newArrayList("error1"));
when(validator2.validate(info)).thenReturn(Lists.newArrayList("error2"));
@Test
public void testComposite() throws Exception {
List<String> errors = compositeValidator.validate(info);
verify(validator1).validate(info);
verify(validator2).validate(info);
assertThat(errors).containsExactly("error1", "error2");
}
}
Cada una de las clases Validator en particular tendría su propia clase específica
de test, en el caso de AgeValidator sería algo como:
@Mock
private NewUserInfo newUserInfo;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void testAdult() throws Exception {
when(newUserInfo.getAge()).thenReturn(20);
@Test
public void testChild() throws Exception {
when(newUserInfo.getAge()).thenReturn(15);