Programação orientada a aspectos com C# Paulo Borba e André Furtado Centro de Informática Universidade Federal de Pernambuco Programação orientada a aspectos é... uma nova técnica de programação que oferece suporte à modularização de crosscutting concerns Ou seja, programação orientada a aspectos é... uma nova técnica de programação que oferece suporte à modularização de “requisitos” que afetam várias partes de uma aplicação Persistência é um crosscutting concern public class Banco { public class CadastroContas { private CadastroContas contas; private RepositorioContas contas; private Banco() { contas = new CadastroConta (new RepositorioContasAccess()); } public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); contas.Atualizar(c); } public void Cadastrar(Conta conta) { Persistence.DBHandler.StartTransaction(); try { contas.Cadastrar(conta); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Persistence.DBHandler.StartTransaction(); try { contas.Transferir(numeroDe, numeroPara, valor); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); contas.Atualizar(de); contas.Atualizar(para); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } } Código de persistência em vermelho… public class RepositorioContasAccess : RepositorioContas { public void Inserir(Conta conta) { string sql = "INSERT INTO Conta (NUMERO,SALDO) VALUES ('" + conta.Numero + "'," + conta.Saldo + ")"; OleDbCommand insertCommand = new OleDbCommand (sql,DBHandler.Connection,DBHandler.Transaction); insertCommand.ExecuteNonQuery(); } public void Atualizar(Conta conta) { string sql = "UPDATE Conta SET SALDO = (" + conta.As ldo + ") WHERE NUMERO = '" + conta.Numero + "'"; OleDbCommand updateCommand = new OleDbCommand(s ql,DBHandler.Connection,DBHandler.Transaction); int linhasAfetadas; linhasAfetadas = updateCommand.ExecuteNonQuery(); if (linhasAfetadas == 0) { throw new ContaNaoEncontradaException(conta.Numero); } } } public class DBHandler { private static OleDbConnection connection; public static OleDbConnection Connection { get { if (connection == null) { string dataSource = "BancoCS.mdb"; string strConexao = "Provider= Microsoft.Jet.OLEDB.4.0; " + "Data Source=" + dataSource; connection = new OleDbConnection(strConexao); } return connection; } } public static OleDbConnection GetOpenConnection() { Connection.Open(); return Connection; } public static void StartTransaction() { Connection.Open(); transaction = Connection.BeginTransaction(); } } public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; } } Crosscutting concerns… Afetam várias partes da aplicação São relativos à decomposição dominante • via classes no caso de OO • via funcões no caso das linguagens funcionais Exemplos de crosscutting concerns Distribuição Controle de concorrência Tratamento de exceções Logging Debugging Variações em linhas de produtos de software Suporte a eventos Há várias técnicas para modularizacao Procedimentos Classes, herança e subtipos Padrões (arquitetura em camadas) Aspectos Foco em modularizar crosscutting concerns As técnicas de modularização... São complementares e ajudam a... • Separar preocupações (separation of concerns) • Aumentar extensibilidade • Facilitar reuso Tudo isso vale para aspectos (crosscutting concerns) Sem aspectos public class Banco { private RepositorioContas contas; private Banco() { contas = new CadastroConta (new RepositorioContasAccess()); } public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); contas.Atualizar(c); } public void Cadastrar(Conta conta) { Persistence.DBHandler.StartTransaction(); try { contas.Cadastrar(conta); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Persistence.DBHandler.StartTransaction(); try { contas.Transferir(numeroDe, numeroPara, valor); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } } public class CadastroContas { private CadastroContas contas; public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); contas.Atualizar(de); contas.Atualizar(para); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } public class RepositorioContasAccess : RepositorioContas { public void Inserir(Conta conta) { string sql = "INSERT INTO Conta (NUMERO,SALDO) VALUES ('" + conta.Numero + "'," + conta.Saldo + ")"; OleDbCommand insertCommand = new OleDbCommand (sql,DBHandler.Connection,DBHandler.Transaction); insertCommand.ExecuteNonQuery(); } public void Atualizar(Conta conta) { string sql = "UPDATE Conta SET SALDO = (" + conta.As ldo + ") WHERE NUMERO = '" + conta.Numero + "'"; OleDbCommand updateCommand = new OleDbCommand(s ql,DBHandler.Connection,DBHandler.Transaction); int linhasAfetadas; linhasAfetadas = updateCommand.ExecuteNonQuery(); if (linhasAfetadas == 0) { throw new ContaNaoEncontradaException(conta.Numero); } } } public class DBHandler { private static OleDbConnection connection; public static OleDbConnection Connection { get { if (connection == null) { string dataSource = "BancoCS.mdb"; string strConexao = "Provider= Microsoft.Jet.OLEDB.4.0; " + "Data Source=" + dataSource; connection = new OleDbConnection(strConexao); } return connection; } } public static OleDbConnection GetOpenConnection() { Connection.Open(); return Connection; } public static void StartTransaction() { Connection.Open(); transaction = Connection.BeginTransaction(); } } public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; } } Com aspectos public class Banco { public class CadastroContas { private CadastroContas contas; private RepositorioContas contas; private Banco() { contas = new CadastroConta public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); } } public void Cadastrar(Conta conta) { contas.Cadastrar(conta); } public void Transferir(string numeroDe, string n umeroPara, double valor) { contas.Transferir(numeroDe, numeroPara, valor); } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } public class RepositorioContasAccess : RepositorioContas { public void Inserir(Conta conta) { string sql = "INSERT INTO Conta (NUMERO,SALDO) VALUES ('" + conta.Numero + "'," + conta.Saldo + ")"; OleDbCommand insertCommand = new OleDbCommand (sql,DBHandler.Connection,DBHandler.Transaction); insertCommand.ExecuteNonQuery(); } public void Atualizar(Conta conta) { string sql = "UPDATE Conta SET SALDO = (" + conta.As ldo + ") WHERE NUMERO = '" + conta.Numero + "'"; OleDbCommand updateCommand = new OleDbCommand(s ql,DBHandler.Connection,DBHandler.Transaction); int linhasAfetadas; linhasAfetadas = updateCommand.ExecuteNonQuery(); if (linhasAfetadas == 0) { throw new ContaNaoEncontradaException(conta.Numero); } } public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; } } public class DBHandler { private static OleDbConnection connection; public static OleDbConnection Connection { get { if (connection == null) { string dataSource = "BancoCS.mdb"; string strConexao = "Provider= Microsoft.Jet.OLEDB.4.0; " + "Data Source=" + dataSource; connection = new OleDbConnection(strConexao); } return connection; } } public static OleDbConnection GetOpenConnection() { Connection.Open(); return Connection; } public static void StartTransaction() { Connection.Open(); transaction = Connection.BeginTransaction(); } } } public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { public class Conta { private string numero; ; } public void Debitar(double valor) { Código base do sistema Código do aspecto de persistência com OleDb Roteiro Problemas com implementações OO Conceitos de OA e Eos Soluções baseadas em aspectos AspectC# e LOOM.NET Conclusões Projeto OO ruim G D COMUNICAÇÃO I D NEGÓCIO S Problema: entrelaçamento de código com diferentes propósitos Projeto OO bom, em camadas Interface com o usuário (GUI) Comunicação Negócio Dados Melhor, em camadas com PDC (sem interfaces) Camada de negócio Camada de dados Boa modularização sem persistência, distribuição, ... public class Programa { [STAThread] public static void Main(string[] args) { Banco fachada = Banco.GetInstance(); Programa.menu(fachada); } public static void menu(Banco fachada) { string numero = null; double valor = 0.0; Conta conta = null; int opcao = 1; while (opcao != 0) { try { System.Console.Out.WriteLine("Aperte <Enter> para continuar"); Util.Util.waitEnter(); System.Console.Out.WriteLine("\n\n\n\n\n\n\n"); System.Console.Out.WriteLine("Escolha uma das alternativas abaixo:"); System.Console.Out.WriteLine("1 - Cadastrar Conta"); System.Console.Out.WriteLine("2 - Creditar"); System.Console.Out.WriteLine("3 - Debitar"); System.Console.Out.WriteLine("4 - Transferir"); System.Console.Out.WriteLine("5 - Ver Saldo"); System.Console.Out.WriteLine("0 - Sair"); opcao = Util.Util.readInt(); switch (opcao) { Dados public class Banco { private RepositorioContas contas; private Banco() { contas = new CadastroConta (new RepositorioContasAccess()); } public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); public void Cadastrar(Conta conta) { contas.Cadastrar(conta); } public void Transferir(string numeroDe, string n umeroPara, double valor) { contas.Transferir(numeroDe, numeroPara, valor); } } public class CadastroContas { private CadastroContas contas; } public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } public class RepositorioContasArray : RepositorioContas { private Conta[] contas; private int indice; public RepositorioContasArray() { contas = new Conta[100]; indice = 0; } public void Inserir(Conta conta) { contas[indice] = conta; indice = indice + 1; } public void Atualizar(Conta conta) { int i = GetIndice(conta.Numero); if (i == indice) { throw new ContaNaoEncontradaException(conta.Numero); } else { contas[i].Atualizar(conta); } } Interface Negócio public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; } } Mas temos problemas com persistência via OleDb public class Banco { public class CadastroContas { private CadastroContas contas; private RepositorioContas contas; private Banco() { contas = new CadastroConta (new RepositorioContasAccess()); } public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); contas.Atualizar(c); } public void Cadastrar(Conta conta) { Persistence.DBHandler.StartTransaction(); try { contas.Cadastrar(conta); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Persistence.DBHandler.StartTransaction(); try { contas.Transferir(numeroDe, numeroPara, valor); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); contas.Atualizar(de); contas.Atualizar(para); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } } Código OleDb em vermelho… public class RepositorioContasAccess : RepositorioContas { public void Inserir(Conta conta) { string sql = "INSERT INTO Conta (NUMERO,SALDO) VALUES ('" + conta.Numero + "'," + conta.Saldo + ")"; OleDbCommand insertCommand = new OleDbCommand (sql,DBHandler.Connection,DBHandler.Transaction); insertCommand.ExecuteNonQuery(); } public void Atualizar(Conta conta) { string sql = "UPDATE Conta SET SALDO = (" + conta.As ldo + ") WHERE NUMERO = '" + conta.Numero + "'"; OleDbCommand updateCommand = new OleDbCommand(s ql,DBHandler.Connection,DBHandler.Transaction); int linhasAfetadas; linhasAfetadas = updateCommand.ExecuteNonQuery(); if (linhasAfetadas == 0) { throw new ContaNaoEncontradaException(conta.Numero); } } } public class DBHandler { private static OleDbConnection connection; public static OleDbConnection Connection { get { if (connection == null) { string dataSource = "BancoCS.mdb"; string strConexao = "Provider= Microsoft.Jet.OLEDB.4.0; " + "Data Source=" + dataSource; connection = new OleDbConnection(strConexao); } return connection; } } public static OleDbConnection GetOpenConnection() { Connection.Open(); return Connection; } public static void StartTransaction() { Connection.Open(); transaction = Connection.BeginTransaction(); } } public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; } } Problemas com implementação OO public class Banco { public class CadastroContas { private CadastroContas contas; private RepositorioContas contas; private Banco() { contas = new CadastroConta (new RepositorioContasAccess()); } public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); contas.Atualizar(c); } public void Cadastrar(Conta conta) { Persistence.DBHandler.StartTransaction(); try { contas.Cadastrar(conta); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } Entrelaçamento de código (tangling) public void Transferir(string numeroDe, string n umeroPara, double valor) { Persistence.DBHandler.StartTransaction(); try { contas.Transferir(numeroDe, numeroPara, valor); Persistence.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Persistence.DBHandler.RollBackTransaction(); throw ex; } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); contas.Atualizar(de); contas.Atualizar(para); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } public class DBHandler { private static OleDbConnection connection; public static OleDbConnection Connection { get { if (connection == null) { string dataSource = "BancoCS.mdb"; string strConexao = "Provider= Microsoft.Jet.OLEDB.4.0; " + "Data Source=" + dataSource; connection = new OleDbConnection(strConexao); } return connection; } } public static OleDbConnection GetOpenConnection() { Connection.Open(); return Connection; } public static void StartTransaction() { Connection.Open(); transaction = Connection.BeginTransaction(); } } } Espalhamento de código (scattering) Persistência na fachada public class Banco { private CadastroContas contas;... public void Cadastrar(Conta conta) { try {Pers.DBHandler.StartTransaction(); contas.Cadastrar(conta); ... Pers.DBHandler.CommitTransaction(); } catch (System.Exception ex){ Pers.DBHandler.RollBackTransaction();... } }... Código de negócio misturado com transações (não pode ficar na coleção de dados) Persistência na coleção de negócio public class CadastroContas { private RepositorioContas contas;... public void Creditar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Creditar(valor); contas.Atualizar(c); }... Sincronização de estados entre objetos e tabelas (registros) Problemas e conseqüências Código de persistência misturado com código de negócio Código de persistência aparece em várias classes Código difícil de manter e reusar • mudanças na tecnologia de persistência serão invasivas Implementação OA public class Banco { public class CadastroContas { private CadastroContas contas; private RepositorioContas contas; private Banco() { contas = new CadastroConta public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); } } public void Cadastrar(Conta conta) { contas.Cadastrar(conta); } public void Transferir(string numeroDe, string n umeroPara, double valor) { contas.Transferir(numeroDe, numeroPara, valor); } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } public class RepositorioContasAccess : RepositorioContas { public void Inserir(Conta conta) { string sql = "INSERT INTO Conta (NUMERO,SALDO) VALUES ('" + conta.Numero + "'," + conta.Saldo + ")"; OleDbCommand insertCommand = new OleDbCommand (sql,DBHandler.Connection,DBHandler.Transaction); insertCommand.ExecuteNonQuery(); } public void Atualizar(Conta conta) { string sql = "UPDATE Conta SET SALDO = (" + conta.As ldo + ") WHERE NUMERO = '" + conta.Numero + "'"; OleDbCommand updateCommand = new OleDbCommand(s ql,DBHandler.Connection,DBHandler.Transaction); int linhasAfetadas; linhasAfetadas = updateCommand.ExecuteNonQuery(); if (linhasAfetadas == 0) { throw new ContaNaoEncontradaException(conta.Numero); } } public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; } } public class DBHandler { private static OleDbConnection connection; public static OleDbConnection Connection { get { if (connection == null) { string dataSource = "BancoCS.mdb"; string strConexao = "Provider= Microsoft.Jet.OLEDB.4.0; " + "Data Source=" + dataSource; connection = new OleDbConnection(strConexao); } return connection; } } public static OleDbConnection GetOpenConnection() { Connection.Open(); return Connection; } public static void StartTransaction() { Connection.Open(); transaction = Connection.BeginTransaction(); } } } public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { public class Conta { private string numero; ; } public void Debitar(double valor) { Código base do sistema Código do aspecto de persistência com OleDb Conseqüências Melhor modularidade, reuso e extensibidade modularidade? • mais unidades de código • mudanças na base podem causar impacto nos aspectos Separation of concerns • relação entre os aspectos e o resto do sistema nem sempre é clara Normalmente menos linhas de código Weaving é usado para… Compor a base do sistema com os aspectos Sistema original chamadas locais entre A e B A Processo de composição Sistema distribuído chamadas remotas entre A e B Aspectos de distribuição B Weaver A B Tecnologia de distribução Composição nos join points a method is called and returns or throws object A a method is called and returns or throws dispatch object B dispatch a method executes and returns or throws Comportamento pode ser alterado nos join points… a method executes and returns or throws Fonte: AspectJ Programming Guide Pointcuts especificam join points Identificam joint points de um sistema • chamadas e execuções de métodos (e construtores) • acessos a atributos • tratamento de exceções • inicialização estática e dinâmica Composição de joint points • &&, || e ! Identificando chamadas de métodos nome do pointcut pointcut writeCall(): call(public void any.Write(string)); identifica chamadas de … método Write de qualquer classe com argumento string Advice especifica comportamento extra nos join points Define código adicional que deve ser executado… • before • after — after returning — after throwing • ou around join points Alterando o comportamento de chamadas de métodos após... qualquer chamada a Write dentro de HelloWorld after(): writeCall() && within(HelloWorld) { System.Console.Write(“ AOP World"); } a ação especificada será executada Aspectos agrupam pointcuts, advices, propriedades, etc. aspect HelloAOPWorld { pointcut writeCall(): call(public void any.Write(string)); after(): writeCall() && within(HelloWorld) { System.Console.Write(“ AOP World!"); } } Hello AOP World! public class HelloWorld { public static void Main(string[] args) { System.Console.Write(“Hello"); } } Chamada afetada pelo advice, caso HelloAOPWorld tenha sido composto com HelloWorld Aspecto de persistência, advices public aspect PersistenceAspect { before(): TransMethods() { Pers.DBHandler.StartTransaction(); } after() returning(): TransMethods() { Pers.DBHandler.CommitTransaction(); } after() throwing(): TransMethods() { Pers.DBHandler.RollBackTransaction(); }... dynamic crosscutting com advice… Além de Temos também static crosscutting • alterar relação de subtipo • adicionar membros a classes Inter-type declarations Aspecto de persistência, pointcut call versus execution pointcut TransMethods(): execution(public any Trans.any(..)); private interface Trans { public void Cadastrar(Conta conta);... } declare parents: Banco:Trans; altera a hierarquia de tipos interface local ao aspecto Aspecto de persistência, pointcut, alternativa pointcut TransMethods(): execution(public any Banco.Cadastrar(..)) || execution(public any Banco.Creditar(..)) || execution(public any Banco.Debitar(..)) || ... Declaração entre tipos no aspecto de distribuição Introduz construtor na classe indicada public SaldoInsuficienteException.new( ...SerializationInfo info, ...StreamingContext context): base(info,context) { numero = info.GetString("numero"); saldo = info.GetDouble("saldo"); } Banco com controle de concorrência básico public class Banco { public class CadastroContas { private CadastroContas contas; private RepositorioContas contas; private Banco() { contas = new CadastroConta (new RepositorioContasAccess()); } public void Debitar(string numero, double valor) { lock(this) { Conta c = contas.Procurar(numero); c.Debitar(valor); public void Cadastrar(Conta conta) { contas.Cadastrar(conta); } public void Transferir(string numeroDe, string n umeroPara, double valor) { contas.Transferir(numeroDe, numeroPara, valor); } } } public void Transferir(string numeroDe, string n umeroPara, double valor) { lock(this) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); } } } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } Negócio public class RepositorioContasArray : RepositorioContas { private Conta[] contas; private int indice; public RepositorioContasArray() { contas = new Conta[100]; indice = 0; } public void Inserir(Conta conta) { contas[indice] = conta; indice = indice + 1; } public void Atualizar(Conta conta) { int i = GetIndice(conta.Numero); if (i == indice) { throw new ContaNaoEncontradaException(conta.Numero); } else { if (conta.Timestamp == contas[I].Timestamp) { contas[i].Atualizar(conta); Conta.incTimestamp(); } else {………….}} } } Dados public class Conta { private string numero; private double saldo; Private long timestamp; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; this.timestamp = c.timestamp; } } Concorrência Controle de concorrência na coleção de negócio public void Debitar(string n, double v) { lock(this) {... Conta c = contas.Procurar(n); ... c.Debitar(v); } }... Controle de concorrência para evitar interferências indesejadas entre os métodos da fachada Controle de concorrência na coleção de dados public void Atualizar(Conta conta) { ... c = contas[i]; if (conta.Timestamp == c.Timestamp) { c.Atualizar(conta); c.UpdateTimestamp(); } else {...} }... Controle de concorrência para evitar manipulação de versões desatualizadas de objetos Controle de concorrência na classe básica public class Conta { private double saldo;... private long timestamp; public void Atualizar(Conta c) { this.saldo = c.saldo;... this.timestamp = c.timestamp; }... } Controle otimista de concorrência Os pointcuts podem expor o contexto dos join points Informações disponíveis nos join points • • • • argumentos de métodos objetos responsáveis pela execução objetos alvo variáveis de instância Aspecto de concorrência, pointcut Informação exposta public aspect ConcurrencyAspect { pointcut ConcurrentMethods(object t): ((call(void CadContas.Cadastrar(Conta)) && target(t)) || ... ); Associação de t com o alvo da chamada de método Aspecto de concorrência, advice O corpo do Object around(Object t): ConcurrentMethods(t) { Object obj; around poderá usar a informação exposta lock (t) { obj = proceed(t); } return obj; } A execução do join point interceptado deve continuar Aspecto de persistência, outro advice atribuições à void around(Banco b): variável de instância ou estática fset(CadastroContas Banco.contas) && execution(private Banco.new()) && this(b) { b.contas = new CadastroContas( new RepositorioContasAccess()); } Associação de b com o objeto sendo inicializado Quebra de encapsulamento void around(Banco b): fset(CadastroContas Banco.contas) && execution(private Banco.new()) && this(b) { b.contas = new CadastroContas( new RepositorioContasAccess()); } Variável de instância privada! Aspecto deve ser definido como privileged Banco remoto com .NET Remoting Distribuição public class Programa { Interface Negócio public class Banco: System.MarshalByRefObject { public static void menu(Banco fachada) { string numero = null; while (opcao != 0) { try { System.Console.Out.WriteLine("Aperte <Enter> para continuar"); Util.Util.waitEnter(); System.Console.Out.WriteLine("\n\n\n\n\n\n\n"); System.Console.Out.WriteLine("2 - Creditar"); …} catch (Exception exception) { System.Console.Out.WriteLine(exception.Message); } public class CadastroContas { private CadastroContas contas; private RepositorioContas contas; private Banco() { contas = new CadastroConta (new RepositorioContasAccess()); } public void Debitar(string numero, double valor) { Conta c = contas.Procurar(numero); c.Debitar(valor); contas.Atualizar(c); } public void Cadastrar(Conta conta) { contas.Cadastrar(conta); } public void Transferir(string numeroDe, string n umeroPara, double valor) { contas.Transferir(numeroDe, numeroPara, valor); } } [STAThread] public static void Main(string[] args) { Banco fachada; try { TcpChannel chan = new TcpChannel(); (Banco)Activator.GetObject(typeof(Fachada.Banco), System.Console.WriteLine("Could not locate server"); } else { Programa.menu(fachada); } } catch (Exception e) { System.Console.WriteLine("The error was: " + e.Message); } } public void Transferir(string numeroDe, string n umeroPara, double valor) { Conta de = contas.Procurar(numeroDe); Conta para = contas.Procurar(numeroPara); de.Debitar(valor); para.Creditar(valor); contas.Atualizar(de); contas.Atualizar(para); } public double Saldo(string numero) { Conta c = contas.Procurar(numero); return c.Saldo; } } } public class ServerInit { public static void Main(string[] args) { try { TcpChannel chan = new TcpChannel(8085); ChannelServices.RegisterChannel(chan); RemotingConfiguration.RegisterWellKnownServiceType (Type.GetType("Fachada.Banco"), } catch (Exception e) { Console.WriteLine("An error has happened:"); Console.WriteLine(e.Message); } } public class RepositorioContasArray : RepositorioContas { private Conta[] contas; private int indice; public RepositorioContasArray() { contas = new Conta[100]; indice = 0; } public void Inserir(Conta conta) { contas[indice] = conta; indice = indice + 1; } public void Atualizar(Conta conta) { int i = GetIndice(conta.Numero); if (i == indice) { throw new ContaNaoEncontradaException(conta.Numero); } else { contas[i].Atualizar(conta); } } Dados [System.Serializable()]public class Conta { private string numero; private double saldo; public void Creditar(double valor) { this.saldo = this.saldo + valor; } public void Debitar(double valor) { if (valor > saldo) { throw new SaldoInsuficienteException)); } else { this.saldo = this.saldo - valor; } } public void Atualizar(Conta c) { this.numero = c.numero; this.saldo = c.saldo; } } [System.Serializable()] public class ContaJaCadastradaException : System.Exception { public string Numero { get { return numero; } } private string numero; public ContaJaCadastradaException(numero; } public ContaJaCadastradaExceptionnumero = info.GetString("numero"); } public override void GetObjectData(base. GetObjectData(info,context); info.AddValue("numero", numero,typeo f(string)); }} Distribuição na interface com o usuário public static void Main(string[] args) { Banco fachada; try {... fachada = (Banco) Activator.GetObject( typeof(Fachada.Banco), "tcp://localhost:8085/BancoRemoto"); ... Programa.menu(fachada); } catch... serviço de } lookup Distribuição na fachada public class Banco: System.MarshalByRefObject { private CadastroContas contas; private static Banco banco; Manipulação de objetos por valor versus por referência Supertipo de qualquer servidor Distribuição na classe básica e exceções [System.Serializable()] public class Conta { private double saldo;...} [System.Serializable()] public class ContaNaoEncontradaException : System.Exception {...} Possibilita a passagem de objetos por valor Aspecto de distribuição, servidor public aspect DistributionAspectServer { declare parents: Banco:System.MarshalByRefObject; } Temos dois aspectos apenas por questões de implantação Aspecto de distribuição, cliente Acrescenta atributos a uma classe aspect DistributionAspectClient { declare class attributes: (Conta || SaldoInsuficienteException) [System.Serializable()]; ... Aspecto de distribuição void around(): execution(...void Programa.Main(string[])) { Banco fachada; try {... fachada = (Banco) Activator.GetObject( typeof(Fachada.Banco), "tcp://localhost:8085/BancoRemoto"); ...Programa.menu(fachada); } catch... } O aspecto de sincronização de estado é... Útil tanto para persistência quanto para distribuição Dividido em duas partes: • registro de objetos modificados (sujos) • atualização dos objetos modificados Registro de objetos modificados Quaisquer argumentos pointcut localUpdate(Conta c): this(CadContas) && target(c) && (call(any Conta.Creditar(..)) || ...; private ArrayList dirtyObjects = ...; after(Conta c) returning(): localUpdate(c) { dirtyObjects.add(c); } Atualização dos objetos modificados pointcut localExecution(CadContas cad): if(HasDirtyObjects()) && this(cad) && execution(public any any(..)); after(CadContas cad) returning(): localExecution(cad) { foreach (Conta c in dirtyObjects) { cad.Contas.Atualizar(c); } } Aspecto de sincronização de estado publics omitidos... privileged aspect StateSynchronization percflow(execution(any Banco.any(..))) { RepositorioContas CadastroContas.Contas { get { return this.contas; } } Para evitar interferências no atributo do aspecto Adiciona propriedade Instâncias de aspectos default, apenas uma instância, aspecto estático percflow, uma instância para cada fluxo de um joint point pertarget, uma instância para cada alvo de um joint point pertthis percflowbelow Outros pointcut designators cflow(<pointcut>) • todos os join points no fluxo de controle do <pointcut> cflowbelow(<pointcut>) • todos os join points no fluxo de controle do <pointcut>, exceto o inicial fget(<signature>) • todos os join points dos acessos às variáveis com assinatura <signature> Mais pointcut designators withincode(<method>) • todos os join points do código que aparece dentro de um método initialization(<constructor>) • todos os join points de inicialização com uma dada assinatura args(<Type or Id, ...>) • todos os join points com argumentos dos tipos especificados Mais construções de Eos NomeAspecto.aspectOf() • retorna a instância de um determinado aspecto, útil para acesso a membros do aspecto declare precedence: <TypeList> • define a precedência entre aspectos que afetam um mesmo join point Advices só para instâncias específicas instancelevel • modificador de advice e aspecto • advice ou aspecto só afetará instâncias registradas pelos métodos abaixo addObject removeObject Selecionando instâncias public aspect Trace { public void Select(Bit bit) { addObject(bit); } after():execution(public any any.any()) { Console.WriteLine("In any method"); } instancelevel after(): execution(public bool Bit.Get()) { Console.WriteLine(“A selective advice"); }... Fonte: Eos distribution Sincronização entre conjuntos, aspecto public instancelevel aspect Bijection { bool busy; Set A; Set B; public void Relate(Set A, Set B) { addObject(A); addObject(B); this.A = A; this.B = B; } Fonte: Eos distribution Sincronização entre conjuntos, advice after():execution(public bool Set.Insert()) { ...r = (bool) thisJoinPoint.getReturnValue(); if(r) { Set set = (Set) thisJoinPoint.getTarget(); object[] args = thisJoinPoint.getArgs(); Element e = (Element)arguments[0]; if (set == this.A) B.Insert(e); else A.Insert(e); } }... Fonte: Eos distribution Sincronização entre conjuntos, inicialização public static void Main (string[] argument) { Set A = new Set(); Set B = new Set(); Bijection bj = new Bijection(); bj.Relate(A,B); Element A1 = new Element("A1"); A.Insert(A1); Element B1 = new Element("B1"); B.Insert(B1);... } Fonte: Eos distribution Acessando mais informações dos join points Além das informações expostas pelos pointcuts, é possível acessar mais sobre os join points usando referências especiais: •thisJoinPoint •thisJoinPointStaticPart Métodos das referências especiais thisJoinPoint getArgs() getTarget() getReturnValue() thisJoinPointStaticPart getSignature() Debugging ou logging simples public aspect LoggingAspect { before(): execution(public any any.any(..)) { System.Console.Write("A method from..." + thisJoinPoint.getTarget() + " is about" + “ to be executed. Its signature is " + thisJoinPointStaticPart.getSignature()); } Aspecto de desenvolvimento versus produção after(): execution(public any any.any(..)) { System.Console.Write("Now the method" + ... + " has finished"); ... System.Console.Write("The return value was “ + thisJoinPoint.getReturnValue()); } Abordagens para aspectos com C# Eos • baseada em uma extensão de C# com novos recursos linguísticos AspectC# • baseada em XMLs que descrevem como classes C# devem ser compostas LOOM.NET • baseada em API e atributos C# que descrevem como classes C# devem ser compostas Orientação a aspectos é... Quantificação (quantification) • uma parte do programa tem efeito em várias outras • o aspecto afeta várias classes e outros aspectos Mudanças não invasivas (obliviouness) • uma parte A do programa tem efeito sobre uma B sem precisar alterar o código de B AspectC# Advices disponíveis: before, after e around Acesso reflexivo ao contexto do join point Falta quantificação AspectC#, localização da base e dos aspectos <Aspect> <TargetBase>c:\...</TargetBase> <AspectBase>c:\...</AspectBase> AspectC#, definição do aspecto (advice) ... <Aspect-Method> <Name>AspectoSincronizacao</Name> <Namespace>Contas</Namespace> <Class>AspectoSincronizacao</Class> <Method>Sincronizar()</Method> </Aspect-Method> AspectC#, definição da classe afetada (pointcut) ...<Target> <Namespace>Contas</Namespace> <Class>CadContas</Class> <Method> <Name>Cadastrar()</Name> <Type>before</Type> <Aspect-Name>AspectoSincronizacao </Aspect-Name></Method></Target></Aspect> LOOM.NET Advices disponíveis: before, after e instead (around) • proceed(context) Aspectos são classes que herdam de Aspect Suporte a quantificação (wildcards) É invasiva LOOM.NET, invasão public class HelloWorld { public virtual void SayHello() { System.Console.WriteLine("Hello World"); } public virtual void SayBye() { System.Console.WriteLine("Bye World"); } } Métodos afetados têm que ser virtual LOOM.NET, aspecto public class LoggingAspect:Aspect { [Call(Invoke.After)] public void SayBye() { System.Console.Write("SayBye! <-"); }... Corpo do método é o corpo do advice; atributo e nome do método são o pointcut e tipo do advice LOOM.NET, quantificação Nome do método deixa de contribuir para o pointcut [Call(Invoke.Before,Alias="Say*")] public void Say() { System.Console.Write(“-> SayHello!"); } LOOM.NET, mais invasão class MainClass { [STAThread] public static void Main(string[] args) { HelloWorld h = (HelloWorld) Weaver.CreateInstance( typeof(HelloWorld), null,new LoggingAspect()); h.SayHello();... }... AOP ou um bom projeto OO? Decorator ou Adapter Com padrões (adapter ou decorator)… Escrevemos mais código A ligação entre o adaptador e o objeto adaptado • é explicita e invasiva • não altera o comportamento de chamadas internas para o objeto adaptado • não tem acesso ao objeto fonte (source) • pode ser modificado dinamicamente Reuso e extensibilidade de aspectos via padrões e frameworks Tag interface • como na interface Trans do aspecto de transações Glue aspects • advices invocando serviços auxiliares Template pointcut • com aspectos abstratos Aspecto abstrato para persistência public abstract aspect APersistenceAspect { abstract pointcut TransactionalMethods(); before(): TransactionalMethods() { Pers.DBHandler.StartTransaction(); }... } Aspecto concreto para persistência public aspect PersistenceAspect: APersistenceAspect { private interface Trans { public void Cadastrar(Conta conta);... } declare parents: Banco:Trans; override pointcut TransactionalMethods(): execution(public any Trans.any(..)); } Aspectos, pontos positivos Útil para implementar crosscutting concerns Modularidade, reuso, e extensibilidade de software Produtividade Separação na implementação e testes (plug-in/out) Aspectos, pontos negativos Modularidade relativa • falta noção de interface Dependência entre classes e aspectos • sensível a refactorings Necessidade de refactorings para expor join points É essencial usar IDE Conflitos entre aspectos Tópicos de pesquisa Orientação a aspectos modular • entendimento das partes leva ao entendimento do todo; não é preciso expandir... Novos joint points Processo de desenvolvimento Linhas de produtos de software Referências Ver roteiro de exercícios ... [email protected]