Olá pessoal, tudo certo?
Depois de alguns dias falando sobre conceitos, escrevendo muito texto e pouco código, resolvi fazer algo diferente. O post de hoje será predominantemente código. É simples e mais direto. Espero que gostem.
O que proponho hoje é uma abstração. Estou considerando a possibilidade de “extrair” comportamentos dos objetos, deixando-os mais simples e diretos. Todos os comportamentos são escritos em classes “utilitárias” que geram objetos que se “anexam” nos objetos básicos. Todo o conceito que apresento hoje é uma abstração dos Behaviors do Silverlight (criados para o Expression Blend).
Sem mais delongas…
Escrevendo objetos com pontos de extensão
Minha proposta, para o post de hoje, é escrever classes de modelo com pouca ou nenhuma validação. Em minha proposta de hoje, todo comportamento “extendido” é anexado aos objetos do modelo.
Escrever código “extensível” é, de algumas formas, trabalhoso. O exemplo que apresento hoje dá uma boa idéia do que considero necessário para escrever objetos que aceitem extensão de comportamento. Observe:
public class Size : INotifyPropertyChanged, INotifyPropertyChanging
{
double WidthField = 0d;
public double Width {
get
{ return this.WidthField; }
set
{
if (value == this.WidthField) return;
this.RaisePropertyChanging("Width", this.WidthField, value);
this.WidthField = value;
this.RaisePropertyChanged("Width");
}
}
double HeightField = 0d;
public double Height
{
get
{ return this.HeightField; }
set
{
if (value == this.HeightField) return;
this.RaisePropertyChanging("Height", this.HeightField, value);
this.HeightField = value;
this.RaisePropertyChanged("Height");
}
}
double DepthField = 0d;
public double Depth
{
get
{ return this.DepthField; }
set
{
if (value == this.DepthField) return;
this.RaisePropertyChanging("Depth", this.DepthField, value);
this.DepthField = value;
this.RaisePropertyChanged("Depth");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
this.OnPropertyChanged(propertyName);
if (this.PropertyChanged == null)
return;
this.PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged
(string propertyName)
{}
public event PropertyChangingEventHandler PropertyChanging;
private void RaisePropertyChanging< T >(string propertyName,
T oldValue, T newValue)
{
this.OnPropertyChanging(propertyName, oldValue, newValue);
if (this.PropertyChanging == null)
return;
this.PropertyChanging(this,
new PropertyChangingEventArgs< T >(propertyName, oldValue, newValue));
}
protected virtual void OnPropertyChanging< T >
(string propertyName, T oldValue, T newValue)
{}
public double GetValue(string propertyName)
{
if (propertyName == "Width") return WidthField;
if (propertyName == "Height") return HeightField;
if (propertyName == "Depth") return DepthField;
throw new ArgumentOutOfRangeException();
}
public void SetValue(string propertyName, double value)
{
if (propertyName == "Width") Width = value;
else if (propertyName == "Height") Height = value;
else if (propertyName == "Depth") Depth = value;
else throw new ArgumentOutOfRangeException();
}
public override string ToString()
{
return string.Format("{0};{1};{2}",
this.WidthField,
this.HeightField,
this.DepthField);
}
}
Observe como todas as propriedades disparam eventos de início e fim de alteração. Esses eventos permitem a objetos externos saibam o que está acontecendo e, eventualmente, façam alguma interferência.
Repare que o próprio framework oferece interfaces públicas para esse propósito. Pessoalmente, consideraria interessante cirar métodos “OnXChanging” e “OnXChanged” para cada propriedade, além de eventos adequados.
Outro artifício que considero bacana é a criação de métodos genéricos para Get e Set.
Para contornar uma limitação que considero grave do framework, extendo a classe “args” com propriedades que indicam melhor o estado do objeto e as alteraçãoes que estão sendo efetuadas. Observe:
public class PropertyChangingEventArgs< TProperty > : PropertyChangingEventArgs
{
public TProperty OldValue { get; private set; }
public TProperty NewValue { get; private set; }
public PropertyChangingEventArgs(
string propertyName,
TProperty oldValue, TProperty newValue) :
base (propertyName)
{
this.OldValue = oldValue;
this.NewValue = newValue;
}
}
Perceba como esse tipo simples pode colaborar em “objetos externos”.
Reproduzindo o conceito de Behaviors do Silverlight (um belo conceito)
Todo modelo desenvolvido pode ser, potencialmente, extendido por comportamentos. Considero muito importante “desenhar” um mecanismo padrão para “plugar” comportamentos a objetos.
Eis minha proposta: Tudo começa com uma interface simples que define as operações de “ligar/desligar” um comportamento. Observe:
public interface IAttachedObject< T >
{
void Attach(T obj);
void Dettach(T obj);
}
Essa interface define o fundamento para um objeto de comportamento.
public abstract class Behavior< T > : IAttachedObject< T >
{
public void Attach(T obj)
{
if (obj == null) return;
this.OnAttach(obj);
}
protected virtual void OnAttach(T obj)
{
return;
}
public void Dettach(T obj)
{
if (obj == null) return;
this.OnDettach(obj);
}
protected virtual void OnDettach(T obj)
{
return;
}
}
Essa é, essencialmente, a idéia desenvolvida para os Behaviors que vemos no Silverlight. Ou seja, nada aqui é novidade.
Repare como foi simples reproduzir o modelo.
Escrevendo um comportamento simples
A partir do modelo simples que desenvolvemos, escrevemos um comportamento, também, muito simples.
public class AcceptsOnlyPositiveValues : Behavior< Size >
{
protected override void OnAttach(Size obj)
{
obj.PropertyChanging += PropertyChanging;
}
void PropertyChanging(object sender, PropertyChangingEventArgs e)
{
var eT = (PropertyChangingEventArgs< double >)e;
if (eT.NewValue <= 0) throw new InvalidOperationException(
e.PropertyName + " accepts only positive values");
}
}
Repare como o modelo é fácil “anexar” esse comportamento a nosso modelo:
var s = new Size(); var b = new AcceptsOnlyPositiveValues(); b.Attach(s);
Nosso modelo simples, original, passou a não aceitar mais valores negativos em nenhuma de suas propriedades.
Um comportamento mais “complexo”
Para fechar nosso post de hoje, mostro um comportamento mais complexo. A idéia é fazer com que, ao alterar uma medida, as demais sejam atualizadas proporcionalmente. Observe:
public class MantainAspectRatioBehavior : Behavior< Size >
{
string[] Properties = { "Width", "Height", "Depth" };
private int GetPropertyIndex(string propertyName)
{
for (int i = 0; i < Properties.Length; i++)
if (propertyName == Properties[i])
return i;
return -1;
}
public Size TargetObject { get; private set; }
protected override void OnAttach(Size obj)
{
if (this.TargetObject != null)
throw new InvalidOperationException();
this.TargetObject = obj;
obj.PropertyChanging += PropertyChanging;
obj.PropertyChanged += PropertyChanged;
base.OnAttach(obj);
}
protected override void OnDettach(Size obj)
{
if (this.TargetObject != obj)
throw new InvalidOperationException();
obj.PropertyChanging -= PropertyChanging;
obj.PropertyChanged -= PropertyChanged;
base.OnDettach(obj);
}
int WorkingOn = -1;
double oldvalue;
void PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (!this.Properties.Contains(e.PropertyName))
return;
bool busy = !(
WorkingOn == -1 ||
WorkingOn == GetPropertyIndex(e.PropertyName)
);
if (busy) return;
WorkingOn = GetPropertyIndex(e.PropertyName);
oldvalue = GetValue(sender as Size, WorkingOn);
}
void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!this.Properties.Contains(e.PropertyName))
return;
if (GetPropertyIndex(e.PropertyName) != WorkingOn) return;
var s = sender as Size;
var newValue = s.GetValue(e.PropertyName);
var ratio = newValue / oldvalue;
var p = WorkingOn;
SetValue(s, (p + 1) % 3, GetValue(s, (p + 1) % 3) * ratio);
SetValue(s, (p + 2) % 3, GetValue(s, (p + 2) % 3) * ratio);
WorkingOn = -1;
}
double GetValue(Size s, int propertyIndex)
{
return s.GetValue(Properties[propertyIndex]);
}
void SetValue(Size s, int propertyIndex, double value)
{
s.SetValue(Properties[propertyIndex], value);
}
}
Perceba que esse comportramento não suporta mais de um objeto “target” sendo tratado ao mesmo tempo.
Behaviors podem ser mesmo divertidos, não acha?!
Por hoje, era isso
![]()
Márcio Fábio Althmann
março 9, 2011
Cara sensacional o post, como conversamos no msn e colocou no Twitter poderia chamar “Fechado para modificações, aberto para extensões” hehe, e os Behaviors do Silverlight são ótimos.
Parabéns pelo post.
Eleriane Cristina costa
março 9, 2011
Muito show.. e divertido..rs
Anotado para olhar com carinho
Eleriane