SlideShare una empresa de Scribd logo
1 de 82
Descargar para leer sin conexión
Principios del diseño
orientado a objetos (OOD)
aplicados en C
Ing. Leandro Francucci (francuccilea@gmail.com)
Vortex
10 de agosto de 2016
1
Objetivo
Comprender los principios fundamentales del diseño orientado a
objetos (OOD) aplicados al desarrollo de software en lenguaje C.
Y por medio de estos:
 Fomentar la producción de software flexible, fácil de mantener
y reutilizable.
 Lograr un diseño de software adaptable a los cambios, hoy
conocido como diseño ágil.
 Disminuir el tiempo, complejidad y costo del desarrollo.
2
Agenda
 Comprender y aplicar el encapsulamiento y la modularidad en
lenguaje C
 Aplicación del proceso de análisis, generalización, parametrización e
instanciación de una solución general a un problema específico.
 Identificación de instancias por referencia y por descriptores.
 Creación de instancias mediante los patrones: asignación dinámica de
memoria, asignación estática de memoria, asignación desde un pool.
 Abstracción mediante punteros opacos, aplicando los métodos por
definición privada y definición privada parcial.
 Utilizar abstracciones, mediante la herencia y el polimorfismo en
lenguaje C
 Abstracciones. ¿Porqué y cuándo aplicarlas?. Definición y aplicación
de interfaces y operaciones polimórficas.
 Selección de implementación de interfaces en tiempo de compilación
y en tiempo de ejecución.
3
Encapsulamiento,
modularidad e instancias4
Instancia única o singleton
 Supongamos que un determinado sistema procesa comandos
recibidos asincrónicamente desde un único canal de
comunicaciones.
 Los cuales se constituyen por cadenas de caracteres ASCII.
 La entidad que los interpreta y procesa, se ejecuta de manera
independiente, en contexto y tiempo, a la recepción de los mismos.
 Por tal motivo, el sistema utiliza una estructura de datos cola, ya que la
misma permite almacenar en forma ordenada una cierta cantidad de
elementos, en este caso caracteres ASCII del tipo char, para luego, en
otro contexto y tiempo, ser extraídos y procesados acordemente,
desacoplando así, la recepción del procesamiento de los datos.
 Particularmente, en esta estructura, los elementos se extraen en orden
cronológico, desde el más antiguo al más reciente, esto determina
que la cola sea una estructura de datos FIFO, en la cual el primer
elemento agregado será el primero en extraerse.
 Generalmente, se implementa como un buffer circular o una lista
enlazada.
5
Módulo ChQueue
 Archivo de inclusión, ChQueue.h.
 Define sus características (atributos
y operaciones) públicas.
6
 Parte del archivo de
implementación, ChQueue.c
 Define las características privadas
Observaciones
 Tanto las operaciones como los atributos de la cola de caracteres
se concentran (encapsulan) en un único par de archivos, donde el
archivo ChQueue.h hace visibles las variables y operaciones
públicas, mientras que el archivo de implementación ChQueue.c
mantiene el cuerpo de las funciones públicas y privadas, al igual
que sus variables.
 Por lo general, el nombre de las operaciones públicas de un
módulo con funciones específicas, contiene un prefijo, que se
utiliza para identificar claramente a qué módulo pertenece y
además evitar que se solapen nombres entre módulos del sistema.
7
Módulo BitPattQueue y el
código duplicado
 Ahora, el sistema requiere una nueva cola, esta vez para almacenar
patrones de datos de 48-bits, typedef unsigned char
BITPAT_T[NUM_BYTE_PAT], donde NUM_BYTE_PAT es igual a 6.
 Dado que el sistema ya cuenta con una implementación probada y
funcionando de una cola (ChQueue), la solución inmediata es tan
simple como copiar (duplicar) y adaptar esta última, cambiando
nombres, tipos de datos y funciones, creando así la cola BitPattQueue.
8
¿Qué desventajas
ocasiona esta solución?
Archivo de inclusión, BitPattQueue.h
Generalización
Evitando la duplicación
 Para evitar los potenciales inconvenientes de la solución anterior
(código duplicado), necesitamos un único módulo capaz de
administrar múltiples colas, que pueda adaptarse a las
necesidades funcionales de cada cola en particular. Por lo tanto:
 Partimos de ChQueueue, analizamos los requerimientos de
ambas colas, las comparamos y encontramos sus diferencias.
Estas definirán los parámetros del nuevo módulo.
9
Generalización
 Partimos del módulo ChQueue, probado y en pleno funcionamiento.
 Generalizamos ChQueue, de modo que pueda manipular múltiples
colas, concentrando los datos de cada cola en una única y nueva
entidad, Queue.
 Esto implica que exista una instancia de Queue por cada cola que se
requiera.
 En este contexto, una instancia es simplemente un objeto de tipo
Queue ubicado en memoria.
 Luego, Queue debe proveer al menos las operaciones básicas para
manipular una cola o instancia de Queue.
10
Generalización mediante estructura Queue
Abstracción
 El proceso anterior genera la abstracción Queue.
 La mejor manera de eliminar el código redundante es crear
abstracciones.
 Al eliminarlo, tiende a lograr sistemas más fáciles de entender y
mantener.
 Definición de abstracción: “la amplificación de lo esencial y la
eliminación de lo irrelevante”.
11
Identificando las instancias
 Dado que Queue administra múltiples colas o instancias de
Queue, para asegurar que sus funciones accedan a los datos de
la instancia correcta, necesitamos indicarle de qué instancia se
trata.
 Si bien existen diversas maneras de resolver dicha situación, sólo
exploraremos la identificación de instancias por referencia y por
descriptores.
 Esto está directamente ligado con el tipo de interfaz que presente
Queue.
12
Por referencias13
Archivo de inclusión, Queue.h
Operaciones de ChQueue, por referencia
Observaciones
 La manera más tradicional de indicarle de qué instancia se trata,
consiste en pasar a cada función (operación) de Queue un
puntero a la instancia en cuestión, generalmente de nombre me.
 En este caso, el nombre de las operaciones se forma por
<prefijo>_<operación>(), donde el prefijo proviene del nombre
del módulo.
 La palabra const luego del * en la declaración del puntero me,
asegura que las operaciones no puedan cambiar su valor, o sea,
la instancia sobre la que se intenta operar.
 Si bien el método presentado se emplea asiduamente en
lenguaje C, manifiesta un inconveniente: la definición de la
estructura Queue queda innecesariamente visible (pública) al
sistema.
 ¿Qué potenciales problemas puede ocasionar el hecho de
publicar la estructura de datos interna del módulo?
14
Por descriptores15
Definiciones públicas de Queue
por descriptores
Operaciones de ChQueue por descriptores
Creando instancias
 Una instancia de Queue es un objeto de tipo Queue asignado en
memoria, el cual representa en tiempo de ejecución una cola
determinada, como ChQueue o BitPattQueue.
 Por definición, los objetos existen en tiempo de ejecución y en un
momento determinado, lo que significa que pueden existir y dejar de
hacerlo, a criterio del sistema.
 Al instanciar, la memoria asignada se reserva durante la existencia
del objeto, desde su creación hasta su destrucción.
 Existen diversas maneras de implementar la asignación de memoria,
aquí exploraremos los patrones de asignación de memoria: dinámico,
estático y pool de objetos.
 Por "pool" entendemos una colección o lista de objetos disponibles y pre-
asignados en memoria.
 Otro de los métodos muy utilizados en embedded, es el patrón de
bloques de tamaño fijo (no presentado en este tutorial).
 Si bien la aplicación de un patrón u otro se determina según las
características particulares del sistema, es común encontrar que un
mismo embedded system de complejidad media o superior, utilice
uno o varios de los patrones listados.
16
Patrón de asignación
dinámica de memoria
 El hecho que un objeto exista durante un momento determinado,
implica un instante de creación y otro de destrucción.
 Por destrucción, entendemos que el objeto en cuestión ya no se
utiliza en el sistema y en consecuencia, es una buena
oportunidad para que el sistema recupere la memoria que le
asignó, de modo tal que, en principio, el sistema pueda re-
utilizarla.
 En general, esta mecánica produce sistemas óptimos respecto de
la utilización de memoria.
 La manera más tradicional y simple de asignar y liberar memoria
es mediante la asignación dinámica de memoria.
 Sin embargo, presenta dos problemas: (1) la temporización no
determinística durante la asignación y liberación de memoria, y
(2) la fragmentación de memoria, esto último puede provocar
una falla fatal en el sistema.
 Por dichas razones, el uso de este método debe justificarse
claramente, caso contrario, es preferible aplicar un método
alternativo.
17
Patrón de asignación
dinámica de memoria
18
Archivo de inclusión Queue.h
para asignación dinámica de
memoria, con operaciones
“especiales”
 El constructor también suele
nombrarse:
Queue_construct(),
Queue_ctor(),
Queue_create() o
Queue_alloc()
 El destructor también suele
nombrarse:
Queue_destroy(), Queue_d
tor(), Queue_close() o
Queue_free()
Patrón de asignación
dinámica de memoria
19
Implementación de
las operaciones
“especiales”
Patrón de asignación
dinámica de memoria
20
Constructor e inicializador
Simplificación
constructor e
inicializador en una
única operación
Patrón de asignación
dinámica de memoria
21
Parte de las operaciones básicas
Inicializador de ChQueue
Patrón de asignación
estática de memoria
22
Inicializador de Chqueue
Patrón de asignación
estática de memoria
23
Constructor de Queue
Patrón de asignación
estática de memoria
24
Inicializador estático en startup
Uso del inicializador estático en startup
 Este patrón puede
aplicarse aún en
sistemas que fueron
concebidos
utilizando el patrón
de asignación
dinámica.
 Por ejemplo,
eliminando el uso
del destructor en
todo el sistema.
Patrón de asignación de
objetos desde un pool
25
Definición de operaciones especiales identificando la instancia
mediante referencias
Definición de operaciones especiales identificando la instancia
mediante descriptores
Patrón de asignación de
objetos desde un pool
26
Implementación de
operaciones
especiales
identificando la
instancia mediante
descriptores
Colección de
objetos o “pool”
Patrón de asignación de
objetos desde un pool
27
Inicializador de ChQueue identificando la
instancia mediante descriptores
Aún más abstracción:
punteros opacos
28
Declara el tipo Queue como un tipo incompleto
En este contexto, un puntero opaco es uno tal que su tipo no está completo o
definido.
No permite instanciar un objeto de tipo Queue
pero si un puntero a ese tipo. ¿Porqué?
Ahora, completamos el tipo incompleto y
podemos declarar tanto punteros como objetos
de tipo Queue.
Método: definición privada29
Este método oculta por
completo la definición de
Queue, por lo tanto sus
atributos son privados y no
visibles al resto del sistema.
 La operaciones mantienen la misma forma que con la definición
basada en referencias.
 De esta manera se refuerza el encapsulamiento y la abstracción.
Método: definición privada30
Este método
impone que la
asignación de
memoria sea
responsabilidad del
módulo Queue
Implementación de
Queue, Queue.c
Uso del método.
Método: definición privada
parcial
31
 Este método publica parte de la definición de Queue. Utiliza la
herencia simple implementada en lenguaje C.
Definición de la estructura pública, en
Queue.h
Definición de la estructura
privada, en Queue.c. Ver
ubicación y tipo del
miembro base
La definición de las operaciones
mantiene la forma.
Método: definición privada
parcial
32
Parte de la implementación
propuesta para Queue.c
PrivQueue agrupa los atributos
privados y públicos.
Método: definición privada
parcial
33
 Su uso no tiene cambio alguno respecto del método anterior
 Puede accederse a los miembros públicos
 El puntero chque es opaco
Abstracción, interfaces y
polimorfismo34
Un simple ejemplo
 Supongamos una función, Message_receive(), que recibe
por argumento un mensaje, Z, que transporta cierta info.
 El mensaje Z, se representa por:
 Message_receive(), recibe y procesa el mensaje Z.
35
Una estructura struct en C es una colección de
una o más variables, agrupadas bajo un mismo
nombre. Estas se denominan miembros o
campos. Frecuentemente, definen los atributos
de la entidad que las agrupa.
Un simple ejemplo
 Supongamos ahora que se necesitan procesar dos nuevos
mensajes, X e Y.
36
Premisas de la solución
 Message_receive() debe ser el único acceso para la recepción
de mensajes.
 Su prototipo debe mantenerse tal como fue concebido.
 Debe ser flexible para soportar la extensión de nuevos mensajes y
modificaciones a los existentes.
 Debe minimizar las modificaciones del programa existente.
37
Reordenando mensajes
 De las estructuras de mensajes X, Y y Z:
 Se ordenan sus miembros e identifican sus atributos comunes
 Se agrega un campo para identificarlos
Luego, se agrupan los
miembros comunes en una
nueva estructura y se re-
definen los mensajes.
38
Mensajes re-definidos
¿Podremos mantener el mismo
prototipo de Message_receive()
para todo mensaje, evitando
modificar el código que la
invoca?
39
timestamp
id
Message
var2
var1
base
MsgX
Base classDerived class
var2
var1
timestamp
id
Storage
La estructura común (base)
se ubica como el primer
miembro de la estructura
derivada. Esto es parte
fundamental de la solución
propuesta
Generación de mensajes
 setMsg() completa
la estructura común
entre mensajes.
 Observar la conversión
de tipo (casting).
40
Descripción
 La conversión de tipo (casting) efectuada es legal,
transportable y garantizado por el standard de C, ya que el
anidamiento de estructuras propuesto siempre alinea el
primer miembro base, al comienzo de las estructuras, de
acuerdo con IEC/IEC9899:TC2 (informalmente C99), es decir,
no hay rellenos (padding) al comienzo de una estructura.
41
Diferentes alternativas que
realizan la conversión de tipo.
Con macros
 Para encapsular la conversión de tipo, y así aumentar la
legibilidad y ocultar los detalles innecesarios, puede
recurrirse al uso de macros.
42
Recepción de mensajes
 Cumple con la premisa de
mantener el prototipo de
Message_receive()
(interface).
 Observar la conversión de
tipo
 Esta función es polimórfica,
¿porqué?.
 En este tipo de función,
¿qué consecuencias tiene el
uso del switch y el miembro
id?
43
Descripción
 Recibe el mensaje como un puntero a Message, extiende
el soporte de nuevos mensajes manteniendo su prototipo
 El campo id permite identificar el tipo de mensaje
 Se realiza el casting apropiado de acuerdo al mensaje
recibido.
 MSG_CAST() permite tratar el puntero m de tipo Message
como uno de tipo MsgX. Por ejemplo:
44
Recepción de mensajes
 Accede y muestra la información básica del mensaje
recibido
45
En resumen
 El anidamiento de estructuras permite mantener el prototipo
de Message_receive(), extendiendo sus servicios.
 El método aplicado, agiliza el soporte de nuevos mensajes y
modificaciones a los atributos asociados.
 Message_receive() recibe punteros a Message, aunque
son referencias a instancias de tipos más elaborados, como
MsgX o MsgY.
 La generación de mensajes: trata un puntero de tipo MsgX
como uno de tipo Message.
 La recepción de mensajes: trata un puntero de tipo
Message como uno de tipo MsgX.
 El miembro id, permite identificar el tipo de mensaje.
46
Formalizando la práctica
¿Qué hay detrás de estas prácticas de programación?
 El método descripto no es simplemente una técnica de
codificación
 Es una implementación en C, que emula el concepto de
herencia de clases de la OOP
 Una clase es una estructura de C, con una particularidad,
además de contener datos (atributos) contiene
comportamiento (operaciones), a diferencia de las
estructuras en C que sólo contienen datos.
 Aún así, el comportamiento puede asociarse
indirectamente a una estructura de C, definiendo
funciones separadamente e incluyendo punteros a estas
dentro de la estructura de C.
47
Herencia
 Es una manera de representar clases que son un “tipo
especializado de” otra clase.
 La clase especializada (derivada o hija) hereda las
características de la clase más general (base o padre).
 Todas las características de la clase padre son también
características de la case derivada.
 Aún así, está permitido especializar y/o extender una clase
más general.
 Permite la reutilización tanto del diseño como el código,
evitando su duplicación.
48
Mensajes derivados
 Del ejemplo anterior, la estructura Message, es común a
todos los mensajes, es la estructura base.
 Las estructuras MsgX, MsgY y MsgZ son derivadas de
Message.
 Las estructuras derivadas extienden los atributos de la
estructura base.
49
Especialización
 Significa que una clase derivada puede redefinir las
operaciones provistas por la clase base.
 Está relacionado con el polimorfismo de lenguajes
orientados a objetos.
 Permite a un mismo nombre de función representar diferentes
comportamientos, de acuerdo al contexto.
 Por supuesto, C no soporta implícitamente el polimorfismo.
Aunque puede imitarse.
¿Cómo podríamos implementar el polimorfismo en C?
50
Extensión
 Significa que una clase derivada define nuevas
características, respecto a su clase base.
 Una de las maneras de implementarla en C, es incluyendo
la clase base como una estructura dentro de la clase
derivada, ubicada específicamente como primer miembro
de esta última.
 Esto se denomina herencia por composición.
51
Diagrama del ejemplo52
id: MsgId
timestamp: TimeStamp
Message
var1: double;
var2: uint32_t
MsgX
var1: uint8[Y_VAR1_SIZE];
MsgY
var1: uint8_t
var2: uint32_t
MsgZ
Relation:
generalization
Derived classes
Extended
attributes
struct MsgX
{
Message base;
double var1;
uint32_t var1;
};
MSGID_X
MSGID_Y
MSGID_Z
<<enumeration>>
MsgId
<<usage>>
Client
Base class
Upcasting
 En términos de la OOP, la conversión de tipo de una clase
derivada a una clase más general se denomina
upcasting. Del ejemplo:
 De manera equivalente con una macro:
53
Downcasting
 En términos de la OOP, la conversión de tipo de una clase
base a una clase derivada se denomina downcasting.
 Aunque no es una práctica 100% segura. ¿Porqué?. Del
ejemplo:
 De manera equivalente con una macro:
54
Herencia simple en C
 La estructura RKH_SBSC_T desciende de RKH_ST_T y esta de
RKH_BASE_T.
typedef struct RKH_BASE_T
{
ruint type;
const char *name;
} RKH_BASE_T;
typedef struct RKH_ST_T
{
struct RKH_BASE_T base;
RKH_ENT_ACT_T enter;
RKH_EXT_ACT_T exit;
RKHROM struct RKH_ST_T *parent;
} RKH_ST_T;
typedef struct RKH_SCMP_t
{
RKH_ST_T st;
RKHROM struct RKH_TR_T *trtbl;
RKH_PPRO_T prepro;
RKHROM void *defchild;
RKHROM struct RKH_SHIST_T *history;
} RKH_SCMP_T;
Instance of the
base structure
RKH_BASE_T
RKH_SBSC_T
RKH_BASE_T
RKH_ST_T
Members added in
the derived structure
RKH_ST_T
Members added in the
derived structure
RKH_SBSC_T
55
Ejemplo simple
e: RKH_SIG_T
nref: rui8_t
pool: rui8_t
RKH_EVT_T
cmd: rui8_t
RPC_REQ_T
reason: rui8_t
RPY_SMS_REJ_T
txtsz: rui8_t
dst: EADR_T
sc: EADR_T
txt: char txt[ PDU_SIZEOF_UD_ASCII ]
REQ_SEND_SMS_T
rmj: rui8_t
rmn: rui8_t
REQ_DIC_REV_T
56
Ejemplo con polimorfismo57
 ComServer, especializa (cambia el
comportamiento de) las funciones
heredadas post() y dispatch()
 Cleaner hereda todas las
características de ActiveObject
 ActiveObject como base provee su
propia implementación de las
funciones init(), post() y
dispatch(). Podría denominarse
comportamiento por defecto.
init()
post()
dispatch()
ActiveObject
post()
dispatch()
ComServer Cleaner
Ejemplo con polimorfismo58
 Command no provee implementación de la función format(),
esta es abstracta.
 Cada una de las estructuras BaudRate, Pin y FacilityLock,
proveen su propia implementación de la función format()
 De esta manera, Client invoca a format() desconociendo
los detalles de su implementación, es decir, se abstrae de esta.
format()
Command
format()
baudrate: uint32_t
BaudRate
format()
actual: char *
new: char *
Pin
format()
facility: char *
oldPwd: char *
newPwd: char *
FacilityLock
Client
¿Porqué estas prácticas?
 Construir software que posea una buena estructura de
modo tal que sea flexible, fácil de mantener y reutilizable.
 La aplicación de estos principios, prácticas y patrones para
mejorar la estructura y legibilidad del software, es un
proceso, no un evento, es decir se aplican continuamente,
manteniendo en todo momento el diseño del sistema tan
simple, claro y expresivo como sea posible.
 A su vez, si el desarrollo se realiza en pequeños incrementos,
aplicando prácticas que provean la disciplina y la
realimentación necesaria, el software se construye
rápidamente, aún ante cambios vertiginosos de
requerimientos.
Todo esto constituyen el Desarrollo Ágil
59
Polimorfismo en C
 Una clase derivada puede especializar o redefinir las
operaciones de su clase base. En C++ funciones virtuales.
 Una alternativa para lograr el polimorfismo en C es
mediante punteros a función y anidamiento de estructuras.
 Aplicando esto último a Message_receive():
60
 Compararla contra su versión con switch y el miembro id.
Tabla de funciones virtuales
 En C se requiere un nivel más de indirección, que permita
acceder a la operación correspondiente según el tipo de
estructura derivada (del ejemplo, mensaje recibido).
 Las operaciones (funciones virtuales) se definen en una
tabla, vtbl (tabla de funciones virtuales).
 La funciones virtuales definen las operaciones de la clase.
 La vtbl es una estructura anidada.
 Toda estructura derivada que requiera especializar o
extender las operaciones heredadas, proveerá su propia
vtbl. Referenciada por el puntero vptr.
61
Implementación en C62
 Ya no se requiere el identificador de tipo
de mensaje, id.
 Se agrega el puntero vptr.
 Toda estructura derivada que lo
requiera, puede proveer su propia tabla
de funciones.
 La tabla de funciones virtuales es
simplemente una estructura, donde
cada miembro es un puntero a función.
 La clase derivada no sufre cambio
alguno respecto de la implementación
sin soporte de funciones virtuales.
¿Porqué?.
 Regla general: un cambio común a
todos los mensajes, sólo impacta en la
clase base. ¿Porqué?.
 ¿Cuál es la razón del const y del
puntero *me?.
Inicialización de vtbl
 Esta práctica requiere que cada instancia inicialice vptr.
 Si la clase derivada sólo hereda el comportamiento de su
base, vptr se inicializa con la tabla de funciones virtuales
de su clase base, podríamos decir adopta el
comportamiento “por defecto”. Aún así, las operaciones
pueden especializarse.
 Si la clase base define operaciones pero no provee
implementación alguna, esta no es “instanciable”. En cuyo
caso el comportamiento lo provee la instancia de la clase
derivada.
 ¿Qué ventajas provee el anidamiento de estructuras en la
tabla de funciones virtuales?
63
Definición de vtbl
 Ya no es necesario el
miembro id de
Message. ¿Porqué?
 ¿Qué diferencias se
observan entre esta
implementación y la
anterior de
Message_receive()?
64
Esquema de memoria
resultante
65
timestamp
vptr receive
Message
var2
var1
base
MsgX MsgXVtbl
control
base
controller
MsgX_receive
MsgVtbl
Base classDerived class
Derived vtbl
class
Base vtbl
class
Virtual
functions
Alternativa
 Agrega el miembro offset a
Message, de forma tal de
independizar la posición
relativa de esta estructura en
cada una de sus derivadas
66
Abstracciones
 Supongamos que disponemos de un sistema que registra y
emite la trazabilidad de una aplicación.
67
Client Tracer
Client Tracer FileOut
 El módulo Tracer almacena los
registros de la trazabilidad en un
archivo determinado, como una
entidad monolítica.
 Ahora, delegamos la emisión de
los registros de Tracer al módulo
FileOut dedicado
exclusivamente a la
manipulación de estos registros
sobre archivos.
Abstracciones68
Client Tracer FileOut
StdOut
SerialOut
SocketOut
 El sistema evoluciona y ahora
requiere emitir los registros a
diferentes medios.
 Por lo tanto, modificamos Tracer
para lograr el soporte necesario.
 De ahora en más, un cambio en
los módulos de salida puede
implicar cambios en el módulo
Tracer.
 ¿Cómo podríamos evitar esto
último?
Abstracciones69
 La mejor manera de aislar o desacoplar dos o más módulos es
mediante una abstracción.
 Si bien las abstracciones están fijas, representan un grupo ilimitado de
posibles comportamientos.
 Ahora, Tracer accede a la abstracción, y por lo tanto permanece
“cerrado” para su modificación, ya que depende de una abstracción
que está fija. Sin embargo, su comportamiento puede extenderse
mediante la creación de nuevos módulos derivados de la abstracción.
 Esto último conforma el Principio de Abierto/Cerrado (OCP).
Tracer <<interface>>
TracerOut
FileOut StdOut SerialOut SocketOut ...
Client
Abstracciones70
 El módulo de alto nivel Tracer no depende de los módulos
de bajo nivel, FileOut, StdOut, etc. Ambos dependen de
la abstracción TracerOut.
 La abstracción no depende de los detalles de la
implementación. Por el contrario, estos dependen de la
abstracción.
 Esto conforma el Principio de Inversión de Dependencias
(DIP)
Tracer <<interface>>
TracerOut
FileOut StdOut SerialOut SocketOut ...
Client
Vinculando la implementación
con la abstracción
71
 Establecida la abstracción, resta vincularla con las
implementaciones. En lenguaje C, podemos utilizar varios
métodos para lograrlo, básicamente:
 Vinculación estática
 Se produce al momento de compilar y/o linkear
 Mediante el preprocesador o mediante el linker
 Vinculación dinámica
 Se efectúa en momento de ejecución
 Mediante punteros a función
 La elección de uno u otro depende de la relación de
compromiso entre sus ventajas y desventajas para una
aplicación determinada.
Vinculación preprocesador72
 La solución más básica
consiste en una macro
que provea la
abstracción.
 Entre otras cuestiones,
todo cambio en la
implementación de la
abstracción provoca
cambios en el código
que utiliza la
abstracción.
Vinculación preprocesador73
 Si afrontamos un
problema de
incompatibilidad entre
la interfaz que necesita
el cliente y la provista
por el servidor,
necesitamos definir un
adaptador que
interceda entre el
cliente y el servidor.
 Difícil determinar que
código se compila
realmente para
diferentes situaciones
(compilación
condicional).
Vinculación por linker74
 TraceOut.h define la
abstracción
 Cada implementación se
define en su propio
archivo, donde se respeta
la firma de la operación
definida en la
abstracción.
 Se elije para linkear el
archivo de la
implementación
requerida.
 Un cambio en la
implementación no
provoca un cambio en el
código que utiliza la
abstracción.
Vinculación dinámica
Singleton
75
 La implementación se
vincula en tiempo de
ejecución.
 El módulo TraceOut
administra una única
instancia (singleton).
 Permite obtener un único
binario ejecutable para
todas las implementaciones.
Vinculación dinámica
Singleton
76
 Cada implementación
se define en un archivo
único.
Vinculación dinámica
Instancia múltiple
77
 El módulo TraceOut
administra múltiples
instancias de un mismo tipo
(clase derivada),
identificadas por el
parámetro me.
Vinculación dinámica
Instancia múltiple
78
 A través de me, cada
operación identifica la
instancia sobre la cual
opera.
 Cada módulo se crea a
partir de TraceOut
mediante la herencia
simple.
 Dentro de cada operación
se realiza un downcast
para acceder a los datos
de la clase derivada.
Principios S.O.L.I.D
 Cada uno de los criterios y métodos presentados se basan
fundamentalmente en estos principios.
 Estos forman parte de la estrategia del desarrollo ágil y adaptativo
de software.
79
Inicial Acrónimo Concepto
S SRP Principio de responsabilidad única (Single Responsibility Principle). Una clase debería tener
una única razón para cambiar.
O OCP
Principio de abierto/cerrado (Open/Closed Principle). Las entidades de software (clases,
módulos, funciones, etc) deben estar abiertas para su extensión, pero cerradas para su
modificación.
L LSP
Principio de sustitución de Liskov (Liskov Substitution Principle). Los subtipos deben ser
sustituibles por sus tipos bases.
I ISP
Principio de segregación de la interfaz (Interface Segregation Principle). la noción de que
“muchas interfaces cliente específicas son mejores que una interfaz de propósito general”
D DIP
Principio de inversión de la dependencia (Dependency Inversion Principle). Las
abstracciones no deberían depender de los detalles. Los detalles deberían depender de
las abstracciones.
Conclusiones
 La aplicación de los principios del OOD, como el
encapsulamiento, la abstracción, la herencia, y el
polimorfismo, como fundamentos del diseño de embedded
software en C, no es una imitación caprichosa de lenguajes
orientados a objetos, sino más bien es la aplicación de
ideas y conceptos maduros y formalizados, que bien
empleados y en su “justa medida” permiten no sólo
aumentar la productividad sino también la calidad de
nuestros desarrollos.
 Y así lograr software más flexible, fácil de mantener y
reutilizable.
80
Preguntas81
Referencias
[1] Leandro Francucci, “Principios de OOP aplicados en C para Embedded Systems”,
Febrary, 2014, embedded-exploited.com.ar
[2] Leandro Francucci, “Modularidad, abstracción y múltiples instancias en C para
Embedded Software”, April, 2014, embedded-exploited.com.ar
[3] Dan Saks, "Alternatives Idioms for Inheritance in C", Embedded.com, April 2, 2013.
[4] Robert C. Martin, “Clean Code”, Prentice Hall, 2009
[5] Robert C. Martin, “Agile Principles Patterns and Practices in C#”, Pearson Education, 20
jul. 2006
[6] James W. Grenning, “Test Driven Development for Embedded C”, Pragmatic Bookshelf,
2011
[7] Kernighan & Ritchie, "C Programming Language (2nd Edition)", April 1, 1988.
82

Más contenido relacionado

La actualidad más candente

Unidad 2 sist. oper. 1
Unidad 2 sist. oper. 1Unidad 2 sist. oper. 1
Unidad 2 sist. oper. 1honeyjimenez
 
INF-324 01 07 Procesos
INF-324 01 07 ProcesosINF-324 01 07 Procesos
INF-324 01 07 ProcesosOscarSanchezD
 
Programación multitarea
Programación multitareaProgramación multitarea
Programación multitareabowelmx
 
Descripcion y control de procesos
Descripcion y control de procesosDescripcion y control de procesos
Descripcion y control de procesosLlabajo Baez
 
Arquitectura del desarrollo del software
Arquitectura del desarrollo del softwareArquitectura del desarrollo del software
Arquitectura del desarrollo del softwareJose Morales
 
Cesnavarra 2009-boletín 3
Cesnavarra 2009-boletín 3Cesnavarra 2009-boletín 3
Cesnavarra 2009-boletín 3Cein
 

La actualidad más candente (9)

Unidad 2 sist. oper. 1
Unidad 2 sist. oper. 1Unidad 2 sist. oper. 1
Unidad 2 sist. oper. 1
 
Procesamiento superescalar
Procesamiento superescalarProcesamiento superescalar
Procesamiento superescalar
 
INF-324 01 07 Procesos
INF-324 01 07 ProcesosINF-324 01 07 Procesos
INF-324 01 07 Procesos
 
Multitarea e hilos en java con ejemplos
Multitarea e hilos en java con ejemplosMultitarea e hilos en java con ejemplos
Multitarea e hilos en java con ejemplos
 
Concurrencia y serialización final 2
Concurrencia y serialización final 2Concurrencia y serialización final 2
Concurrencia y serialización final 2
 
Programación multitarea
Programación multitareaProgramación multitarea
Programación multitarea
 
Descripcion y control de procesos
Descripcion y control de procesosDescripcion y control de procesos
Descripcion y control de procesos
 
Arquitectura del desarrollo del software
Arquitectura del desarrollo del softwareArquitectura del desarrollo del software
Arquitectura del desarrollo del software
 
Cesnavarra 2009-boletín 3
Cesnavarra 2009-boletín 3Cesnavarra 2009-boletín 3
Cesnavarra 2009-boletín 3
 

Similar a Principios del Diseño Orientado a Objetos (OOD) Aplicados en C

Saula ana bdii_t7
Saula ana bdii_t7Saula ana bdii_t7
Saula ana bdii_t7Any Saula
 
Patrones de diseño - Andrés Dorado
Patrones de diseño - Andrés DoradoPatrones de diseño - Andrés Dorado
Patrones de diseño - Andrés Dorado2008PA2Info3
 
FMK Capa de Presentacion
FMK Capa de PresentacionFMK Capa de Presentacion
FMK Capa de Presentacionkaolong
 
Sistemas operativos unidad 2
Sistemas operativos unidad 2Sistemas operativos unidad 2
Sistemas operativos unidad 2Luis Cigarroa
 
Cuestionario
CuestionarioCuestionario
CuestionarioJose Nava
 
Temas de exposiciones teoria de sistemas
Temas de exposiciones teoria de sistemasTemas de exposiciones teoria de sistemas
Temas de exposiciones teoria de sistemasOswaldo Hechenleitner
 
Categotias de sistemas operativo
Categotias de sistemas operativoCategotias de sistemas operativo
Categotias de sistemas operativojaviercollantes
 
Org tutorial struts_2010
Org tutorial struts_2010Org tutorial struts_2010
Org tutorial struts_2010Omar Rios
 
Trabajo de procesos y subprocesos
Trabajo de procesos y subprocesos Trabajo de procesos y subprocesos
Trabajo de procesos y subprocesos Stayci Gonzalez
 
S Incronizacion De Procesos
S Incronizacion De ProcesosS Incronizacion De Procesos
S Incronizacion De ProcesosAcristyM
 
S Incronizacion De Procesos
S Incronizacion De ProcesosS Incronizacion De Procesos
S Incronizacion De ProcesosAcristyM
 

Similar a Principios del Diseño Orientado a Objetos (OOD) Aplicados en C (20)

Saula ana bdii_t7
Saula ana bdii_t7Saula ana bdii_t7
Saula ana bdii_t7
 
Patrones de diseño - Andrés Dorado
Patrones de diseño - Andrés DoradoPatrones de diseño - Andrés Dorado
Patrones de diseño - Andrés Dorado
 
FMK Capa de Presentacion
FMK Capa de PresentacionFMK Capa de Presentacion
FMK Capa de Presentacion
 
Sistemas operativos unidad 2
Sistemas operativos unidad 2Sistemas operativos unidad 2
Sistemas operativos unidad 2
 
Cuestionario
CuestionarioCuestionario
Cuestionario
 
Cuestionario
CuestionarioCuestionario
Cuestionario
 
Temas de exposiciones teoria de sistemas
Temas de exposiciones teoria de sistemasTemas de exposiciones teoria de sistemas
Temas de exposiciones teoria de sistemas
 
Categotias de sistemas operativo
Categotias de sistemas operativoCategotias de sistemas operativo
Categotias de sistemas operativo
 
Org tutorial struts_2010
Org tutorial struts_2010Org tutorial struts_2010
Org tutorial struts_2010
 
Unidad i poo avanzada
Unidad i   poo avanzadaUnidad i   poo avanzada
Unidad i poo avanzada
 
Trabajo de procesos y subprocesos
Trabajo de procesos y subprocesos Trabajo de procesos y subprocesos
Trabajo de procesos y subprocesos
 
Guía1
Guía1Guía1
Guía1
 
Guia herramientas de bd
Guia herramientas de bdGuia herramientas de bd
Guia herramientas de bd
 
Unidad2
Unidad2Unidad2
Unidad2
 
Diseño o.o
Diseño o.oDiseño o.o
Diseño o.o
 
Diseño o.o
Diseño o.oDiseño o.o
Diseño o.o
 
S Incronizacion De Procesos
S Incronizacion De ProcesosS Incronizacion De Procesos
S Incronizacion De Procesos
 
S Incronizacion De Procesos
S Incronizacion De ProcesosS Incronizacion De Procesos
S Incronizacion De Procesos
 
Guía herramientas de BD PHP
Guía herramientas de BD PHPGuía herramientas de BD PHP
Guía herramientas de BD PHP
 
Introduccion a Doctrine 2 ORM
Introduccion a Doctrine 2 ORMIntroduccion a Doctrine 2 ORM
Introduccion a Doctrine 2 ORM
 

Principios del Diseño Orientado a Objetos (OOD) Aplicados en C

  • 1. Principios del diseño orientado a objetos (OOD) aplicados en C Ing. Leandro Francucci (francuccilea@gmail.com) Vortex 10 de agosto de 2016 1
  • 2. Objetivo Comprender los principios fundamentales del diseño orientado a objetos (OOD) aplicados al desarrollo de software en lenguaje C. Y por medio de estos:  Fomentar la producción de software flexible, fácil de mantener y reutilizable.  Lograr un diseño de software adaptable a los cambios, hoy conocido como diseño ágil.  Disminuir el tiempo, complejidad y costo del desarrollo. 2
  • 3. Agenda  Comprender y aplicar el encapsulamiento y la modularidad en lenguaje C  Aplicación del proceso de análisis, generalización, parametrización e instanciación de una solución general a un problema específico.  Identificación de instancias por referencia y por descriptores.  Creación de instancias mediante los patrones: asignación dinámica de memoria, asignación estática de memoria, asignación desde un pool.  Abstracción mediante punteros opacos, aplicando los métodos por definición privada y definición privada parcial.  Utilizar abstracciones, mediante la herencia y el polimorfismo en lenguaje C  Abstracciones. ¿Porqué y cuándo aplicarlas?. Definición y aplicación de interfaces y operaciones polimórficas.  Selección de implementación de interfaces en tiempo de compilación y en tiempo de ejecución. 3
  • 5. Instancia única o singleton  Supongamos que un determinado sistema procesa comandos recibidos asincrónicamente desde un único canal de comunicaciones.  Los cuales se constituyen por cadenas de caracteres ASCII.  La entidad que los interpreta y procesa, se ejecuta de manera independiente, en contexto y tiempo, a la recepción de los mismos.  Por tal motivo, el sistema utiliza una estructura de datos cola, ya que la misma permite almacenar en forma ordenada una cierta cantidad de elementos, en este caso caracteres ASCII del tipo char, para luego, en otro contexto y tiempo, ser extraídos y procesados acordemente, desacoplando así, la recepción del procesamiento de los datos.  Particularmente, en esta estructura, los elementos se extraen en orden cronológico, desde el más antiguo al más reciente, esto determina que la cola sea una estructura de datos FIFO, en la cual el primer elemento agregado será el primero en extraerse.  Generalmente, se implementa como un buffer circular o una lista enlazada. 5
  • 6. Módulo ChQueue  Archivo de inclusión, ChQueue.h.  Define sus características (atributos y operaciones) públicas. 6  Parte del archivo de implementación, ChQueue.c  Define las características privadas
  • 7. Observaciones  Tanto las operaciones como los atributos de la cola de caracteres se concentran (encapsulan) en un único par de archivos, donde el archivo ChQueue.h hace visibles las variables y operaciones públicas, mientras que el archivo de implementación ChQueue.c mantiene el cuerpo de las funciones públicas y privadas, al igual que sus variables.  Por lo general, el nombre de las operaciones públicas de un módulo con funciones específicas, contiene un prefijo, que se utiliza para identificar claramente a qué módulo pertenece y además evitar que se solapen nombres entre módulos del sistema. 7
  • 8. Módulo BitPattQueue y el código duplicado  Ahora, el sistema requiere una nueva cola, esta vez para almacenar patrones de datos de 48-bits, typedef unsigned char BITPAT_T[NUM_BYTE_PAT], donde NUM_BYTE_PAT es igual a 6.  Dado que el sistema ya cuenta con una implementación probada y funcionando de una cola (ChQueue), la solución inmediata es tan simple como copiar (duplicar) y adaptar esta última, cambiando nombres, tipos de datos y funciones, creando así la cola BitPattQueue. 8 ¿Qué desventajas ocasiona esta solución? Archivo de inclusión, BitPattQueue.h
  • 9. Generalización Evitando la duplicación  Para evitar los potenciales inconvenientes de la solución anterior (código duplicado), necesitamos un único módulo capaz de administrar múltiples colas, que pueda adaptarse a las necesidades funcionales de cada cola en particular. Por lo tanto:  Partimos de ChQueueue, analizamos los requerimientos de ambas colas, las comparamos y encontramos sus diferencias. Estas definirán los parámetros del nuevo módulo. 9
  • 10. Generalización  Partimos del módulo ChQueue, probado y en pleno funcionamiento.  Generalizamos ChQueue, de modo que pueda manipular múltiples colas, concentrando los datos de cada cola en una única y nueva entidad, Queue.  Esto implica que exista una instancia de Queue por cada cola que se requiera.  En este contexto, una instancia es simplemente un objeto de tipo Queue ubicado en memoria.  Luego, Queue debe proveer al menos las operaciones básicas para manipular una cola o instancia de Queue. 10 Generalización mediante estructura Queue
  • 11. Abstracción  El proceso anterior genera la abstracción Queue.  La mejor manera de eliminar el código redundante es crear abstracciones.  Al eliminarlo, tiende a lograr sistemas más fáciles de entender y mantener.  Definición de abstracción: “la amplificación de lo esencial y la eliminación de lo irrelevante”. 11
  • 12. Identificando las instancias  Dado que Queue administra múltiples colas o instancias de Queue, para asegurar que sus funciones accedan a los datos de la instancia correcta, necesitamos indicarle de qué instancia se trata.  Si bien existen diversas maneras de resolver dicha situación, sólo exploraremos la identificación de instancias por referencia y por descriptores.  Esto está directamente ligado con el tipo de interfaz que presente Queue. 12
  • 13. Por referencias13 Archivo de inclusión, Queue.h Operaciones de ChQueue, por referencia
  • 14. Observaciones  La manera más tradicional de indicarle de qué instancia se trata, consiste en pasar a cada función (operación) de Queue un puntero a la instancia en cuestión, generalmente de nombre me.  En este caso, el nombre de las operaciones se forma por <prefijo>_<operación>(), donde el prefijo proviene del nombre del módulo.  La palabra const luego del * en la declaración del puntero me, asegura que las operaciones no puedan cambiar su valor, o sea, la instancia sobre la que se intenta operar.  Si bien el método presentado se emplea asiduamente en lenguaje C, manifiesta un inconveniente: la definición de la estructura Queue queda innecesariamente visible (pública) al sistema.  ¿Qué potenciales problemas puede ocasionar el hecho de publicar la estructura de datos interna del módulo? 14
  • 15. Por descriptores15 Definiciones públicas de Queue por descriptores Operaciones de ChQueue por descriptores
  • 16. Creando instancias  Una instancia de Queue es un objeto de tipo Queue asignado en memoria, el cual representa en tiempo de ejecución una cola determinada, como ChQueue o BitPattQueue.  Por definición, los objetos existen en tiempo de ejecución y en un momento determinado, lo que significa que pueden existir y dejar de hacerlo, a criterio del sistema.  Al instanciar, la memoria asignada se reserva durante la existencia del objeto, desde su creación hasta su destrucción.  Existen diversas maneras de implementar la asignación de memoria, aquí exploraremos los patrones de asignación de memoria: dinámico, estático y pool de objetos.  Por "pool" entendemos una colección o lista de objetos disponibles y pre- asignados en memoria.  Otro de los métodos muy utilizados en embedded, es el patrón de bloques de tamaño fijo (no presentado en este tutorial).  Si bien la aplicación de un patrón u otro se determina según las características particulares del sistema, es común encontrar que un mismo embedded system de complejidad media o superior, utilice uno o varios de los patrones listados. 16
  • 17. Patrón de asignación dinámica de memoria  El hecho que un objeto exista durante un momento determinado, implica un instante de creación y otro de destrucción.  Por destrucción, entendemos que el objeto en cuestión ya no se utiliza en el sistema y en consecuencia, es una buena oportunidad para que el sistema recupere la memoria que le asignó, de modo tal que, en principio, el sistema pueda re- utilizarla.  En general, esta mecánica produce sistemas óptimos respecto de la utilización de memoria.  La manera más tradicional y simple de asignar y liberar memoria es mediante la asignación dinámica de memoria.  Sin embargo, presenta dos problemas: (1) la temporización no determinística durante la asignación y liberación de memoria, y (2) la fragmentación de memoria, esto último puede provocar una falla fatal en el sistema.  Por dichas razones, el uso de este método debe justificarse claramente, caso contrario, es preferible aplicar un método alternativo. 17
  • 18. Patrón de asignación dinámica de memoria 18 Archivo de inclusión Queue.h para asignación dinámica de memoria, con operaciones “especiales”  El constructor también suele nombrarse: Queue_construct(), Queue_ctor(), Queue_create() o Queue_alloc()  El destructor también suele nombrarse: Queue_destroy(), Queue_d tor(), Queue_close() o Queue_free()
  • 19. Patrón de asignación dinámica de memoria 19 Implementación de las operaciones “especiales”
  • 20. Patrón de asignación dinámica de memoria 20 Constructor e inicializador Simplificación constructor e inicializador en una única operación
  • 21. Patrón de asignación dinámica de memoria 21 Parte de las operaciones básicas Inicializador de ChQueue
  • 22. Patrón de asignación estática de memoria 22 Inicializador de Chqueue
  • 23. Patrón de asignación estática de memoria 23 Constructor de Queue
  • 24. Patrón de asignación estática de memoria 24 Inicializador estático en startup Uso del inicializador estático en startup  Este patrón puede aplicarse aún en sistemas que fueron concebidos utilizando el patrón de asignación dinámica.  Por ejemplo, eliminando el uso del destructor en todo el sistema.
  • 25. Patrón de asignación de objetos desde un pool 25 Definición de operaciones especiales identificando la instancia mediante referencias Definición de operaciones especiales identificando la instancia mediante descriptores
  • 26. Patrón de asignación de objetos desde un pool 26 Implementación de operaciones especiales identificando la instancia mediante descriptores Colección de objetos o “pool”
  • 27. Patrón de asignación de objetos desde un pool 27 Inicializador de ChQueue identificando la instancia mediante descriptores
  • 28. Aún más abstracción: punteros opacos 28 Declara el tipo Queue como un tipo incompleto En este contexto, un puntero opaco es uno tal que su tipo no está completo o definido. No permite instanciar un objeto de tipo Queue pero si un puntero a ese tipo. ¿Porqué? Ahora, completamos el tipo incompleto y podemos declarar tanto punteros como objetos de tipo Queue.
  • 29. Método: definición privada29 Este método oculta por completo la definición de Queue, por lo tanto sus atributos son privados y no visibles al resto del sistema.  La operaciones mantienen la misma forma que con la definición basada en referencias.  De esta manera se refuerza el encapsulamiento y la abstracción.
  • 30. Método: definición privada30 Este método impone que la asignación de memoria sea responsabilidad del módulo Queue Implementación de Queue, Queue.c Uso del método.
  • 31. Método: definición privada parcial 31  Este método publica parte de la definición de Queue. Utiliza la herencia simple implementada en lenguaje C. Definición de la estructura pública, en Queue.h Definición de la estructura privada, en Queue.c. Ver ubicación y tipo del miembro base La definición de las operaciones mantiene la forma.
  • 32. Método: definición privada parcial 32 Parte de la implementación propuesta para Queue.c PrivQueue agrupa los atributos privados y públicos.
  • 33. Método: definición privada parcial 33  Su uso no tiene cambio alguno respecto del método anterior  Puede accederse a los miembros públicos  El puntero chque es opaco
  • 35. Un simple ejemplo  Supongamos una función, Message_receive(), que recibe por argumento un mensaje, Z, que transporta cierta info.  El mensaje Z, se representa por:  Message_receive(), recibe y procesa el mensaje Z. 35 Una estructura struct en C es una colección de una o más variables, agrupadas bajo un mismo nombre. Estas se denominan miembros o campos. Frecuentemente, definen los atributos de la entidad que las agrupa.
  • 36. Un simple ejemplo  Supongamos ahora que se necesitan procesar dos nuevos mensajes, X e Y. 36
  • 37. Premisas de la solución  Message_receive() debe ser el único acceso para la recepción de mensajes.  Su prototipo debe mantenerse tal como fue concebido.  Debe ser flexible para soportar la extensión de nuevos mensajes y modificaciones a los existentes.  Debe minimizar las modificaciones del programa existente. 37
  • 38. Reordenando mensajes  De las estructuras de mensajes X, Y y Z:  Se ordenan sus miembros e identifican sus atributos comunes  Se agrega un campo para identificarlos Luego, se agrupan los miembros comunes en una nueva estructura y se re- definen los mensajes. 38
  • 39. Mensajes re-definidos ¿Podremos mantener el mismo prototipo de Message_receive() para todo mensaje, evitando modificar el código que la invoca? 39 timestamp id Message var2 var1 base MsgX Base classDerived class var2 var1 timestamp id Storage La estructura común (base) se ubica como el primer miembro de la estructura derivada. Esto es parte fundamental de la solución propuesta
  • 40. Generación de mensajes  setMsg() completa la estructura común entre mensajes.  Observar la conversión de tipo (casting). 40
  • 41. Descripción  La conversión de tipo (casting) efectuada es legal, transportable y garantizado por el standard de C, ya que el anidamiento de estructuras propuesto siempre alinea el primer miembro base, al comienzo de las estructuras, de acuerdo con IEC/IEC9899:TC2 (informalmente C99), es decir, no hay rellenos (padding) al comienzo de una estructura. 41 Diferentes alternativas que realizan la conversión de tipo.
  • 42. Con macros  Para encapsular la conversión de tipo, y así aumentar la legibilidad y ocultar los detalles innecesarios, puede recurrirse al uso de macros. 42
  • 43. Recepción de mensajes  Cumple con la premisa de mantener el prototipo de Message_receive() (interface).  Observar la conversión de tipo  Esta función es polimórfica, ¿porqué?.  En este tipo de función, ¿qué consecuencias tiene el uso del switch y el miembro id? 43
  • 44. Descripción  Recibe el mensaje como un puntero a Message, extiende el soporte de nuevos mensajes manteniendo su prototipo  El campo id permite identificar el tipo de mensaje  Se realiza el casting apropiado de acuerdo al mensaje recibido.  MSG_CAST() permite tratar el puntero m de tipo Message como uno de tipo MsgX. Por ejemplo: 44
  • 45. Recepción de mensajes  Accede y muestra la información básica del mensaje recibido 45
  • 46. En resumen  El anidamiento de estructuras permite mantener el prototipo de Message_receive(), extendiendo sus servicios.  El método aplicado, agiliza el soporte de nuevos mensajes y modificaciones a los atributos asociados.  Message_receive() recibe punteros a Message, aunque son referencias a instancias de tipos más elaborados, como MsgX o MsgY.  La generación de mensajes: trata un puntero de tipo MsgX como uno de tipo Message.  La recepción de mensajes: trata un puntero de tipo Message como uno de tipo MsgX.  El miembro id, permite identificar el tipo de mensaje. 46
  • 47. Formalizando la práctica ¿Qué hay detrás de estas prácticas de programación?  El método descripto no es simplemente una técnica de codificación  Es una implementación en C, que emula el concepto de herencia de clases de la OOP  Una clase es una estructura de C, con una particularidad, además de contener datos (atributos) contiene comportamiento (operaciones), a diferencia de las estructuras en C que sólo contienen datos.  Aún así, el comportamiento puede asociarse indirectamente a una estructura de C, definiendo funciones separadamente e incluyendo punteros a estas dentro de la estructura de C. 47
  • 48. Herencia  Es una manera de representar clases que son un “tipo especializado de” otra clase.  La clase especializada (derivada o hija) hereda las características de la clase más general (base o padre).  Todas las características de la clase padre son también características de la case derivada.  Aún así, está permitido especializar y/o extender una clase más general.  Permite la reutilización tanto del diseño como el código, evitando su duplicación. 48
  • 49. Mensajes derivados  Del ejemplo anterior, la estructura Message, es común a todos los mensajes, es la estructura base.  Las estructuras MsgX, MsgY y MsgZ son derivadas de Message.  Las estructuras derivadas extienden los atributos de la estructura base. 49
  • 50. Especialización  Significa que una clase derivada puede redefinir las operaciones provistas por la clase base.  Está relacionado con el polimorfismo de lenguajes orientados a objetos.  Permite a un mismo nombre de función representar diferentes comportamientos, de acuerdo al contexto.  Por supuesto, C no soporta implícitamente el polimorfismo. Aunque puede imitarse. ¿Cómo podríamos implementar el polimorfismo en C? 50
  • 51. Extensión  Significa que una clase derivada define nuevas características, respecto a su clase base.  Una de las maneras de implementarla en C, es incluyendo la clase base como una estructura dentro de la clase derivada, ubicada específicamente como primer miembro de esta última.  Esto se denomina herencia por composición. 51
  • 52. Diagrama del ejemplo52 id: MsgId timestamp: TimeStamp Message var1: double; var2: uint32_t MsgX var1: uint8[Y_VAR1_SIZE]; MsgY var1: uint8_t var2: uint32_t MsgZ Relation: generalization Derived classes Extended attributes struct MsgX { Message base; double var1; uint32_t var1; }; MSGID_X MSGID_Y MSGID_Z <<enumeration>> MsgId <<usage>> Client Base class
  • 53. Upcasting  En términos de la OOP, la conversión de tipo de una clase derivada a una clase más general se denomina upcasting. Del ejemplo:  De manera equivalente con una macro: 53
  • 54. Downcasting  En términos de la OOP, la conversión de tipo de una clase base a una clase derivada se denomina downcasting.  Aunque no es una práctica 100% segura. ¿Porqué?. Del ejemplo:  De manera equivalente con una macro: 54
  • 55. Herencia simple en C  La estructura RKH_SBSC_T desciende de RKH_ST_T y esta de RKH_BASE_T. typedef struct RKH_BASE_T { ruint type; const char *name; } RKH_BASE_T; typedef struct RKH_ST_T { struct RKH_BASE_T base; RKH_ENT_ACT_T enter; RKH_EXT_ACT_T exit; RKHROM struct RKH_ST_T *parent; } RKH_ST_T; typedef struct RKH_SCMP_t { RKH_ST_T st; RKHROM struct RKH_TR_T *trtbl; RKH_PPRO_T prepro; RKHROM void *defchild; RKHROM struct RKH_SHIST_T *history; } RKH_SCMP_T; Instance of the base structure RKH_BASE_T RKH_SBSC_T RKH_BASE_T RKH_ST_T Members added in the derived structure RKH_ST_T Members added in the derived structure RKH_SBSC_T 55
  • 56. Ejemplo simple e: RKH_SIG_T nref: rui8_t pool: rui8_t RKH_EVT_T cmd: rui8_t RPC_REQ_T reason: rui8_t RPY_SMS_REJ_T txtsz: rui8_t dst: EADR_T sc: EADR_T txt: char txt[ PDU_SIZEOF_UD_ASCII ] REQ_SEND_SMS_T rmj: rui8_t rmn: rui8_t REQ_DIC_REV_T 56
  • 57. Ejemplo con polimorfismo57  ComServer, especializa (cambia el comportamiento de) las funciones heredadas post() y dispatch()  Cleaner hereda todas las características de ActiveObject  ActiveObject como base provee su propia implementación de las funciones init(), post() y dispatch(). Podría denominarse comportamiento por defecto. init() post() dispatch() ActiveObject post() dispatch() ComServer Cleaner
  • 58. Ejemplo con polimorfismo58  Command no provee implementación de la función format(), esta es abstracta.  Cada una de las estructuras BaudRate, Pin y FacilityLock, proveen su propia implementación de la función format()  De esta manera, Client invoca a format() desconociendo los detalles de su implementación, es decir, se abstrae de esta. format() Command format() baudrate: uint32_t BaudRate format() actual: char * new: char * Pin format() facility: char * oldPwd: char * newPwd: char * FacilityLock Client
  • 59. ¿Porqué estas prácticas?  Construir software que posea una buena estructura de modo tal que sea flexible, fácil de mantener y reutilizable.  La aplicación de estos principios, prácticas y patrones para mejorar la estructura y legibilidad del software, es un proceso, no un evento, es decir se aplican continuamente, manteniendo en todo momento el diseño del sistema tan simple, claro y expresivo como sea posible.  A su vez, si el desarrollo se realiza en pequeños incrementos, aplicando prácticas que provean la disciplina y la realimentación necesaria, el software se construye rápidamente, aún ante cambios vertiginosos de requerimientos. Todo esto constituyen el Desarrollo Ágil 59
  • 60. Polimorfismo en C  Una clase derivada puede especializar o redefinir las operaciones de su clase base. En C++ funciones virtuales.  Una alternativa para lograr el polimorfismo en C es mediante punteros a función y anidamiento de estructuras.  Aplicando esto último a Message_receive(): 60  Compararla contra su versión con switch y el miembro id.
  • 61. Tabla de funciones virtuales  En C se requiere un nivel más de indirección, que permita acceder a la operación correspondiente según el tipo de estructura derivada (del ejemplo, mensaje recibido).  Las operaciones (funciones virtuales) se definen en una tabla, vtbl (tabla de funciones virtuales).  La funciones virtuales definen las operaciones de la clase.  La vtbl es una estructura anidada.  Toda estructura derivada que requiera especializar o extender las operaciones heredadas, proveerá su propia vtbl. Referenciada por el puntero vptr. 61
  • 62. Implementación en C62  Ya no se requiere el identificador de tipo de mensaje, id.  Se agrega el puntero vptr.  Toda estructura derivada que lo requiera, puede proveer su propia tabla de funciones.  La tabla de funciones virtuales es simplemente una estructura, donde cada miembro es un puntero a función.  La clase derivada no sufre cambio alguno respecto de la implementación sin soporte de funciones virtuales. ¿Porqué?.  Regla general: un cambio común a todos los mensajes, sólo impacta en la clase base. ¿Porqué?.  ¿Cuál es la razón del const y del puntero *me?.
  • 63. Inicialización de vtbl  Esta práctica requiere que cada instancia inicialice vptr.  Si la clase derivada sólo hereda el comportamiento de su base, vptr se inicializa con la tabla de funciones virtuales de su clase base, podríamos decir adopta el comportamiento “por defecto”. Aún así, las operaciones pueden especializarse.  Si la clase base define operaciones pero no provee implementación alguna, esta no es “instanciable”. En cuyo caso el comportamiento lo provee la instancia de la clase derivada.  ¿Qué ventajas provee el anidamiento de estructuras en la tabla de funciones virtuales? 63
  • 64. Definición de vtbl  Ya no es necesario el miembro id de Message. ¿Porqué?  ¿Qué diferencias se observan entre esta implementación y la anterior de Message_receive()? 64
  • 65. Esquema de memoria resultante 65 timestamp vptr receive Message var2 var1 base MsgX MsgXVtbl control base controller MsgX_receive MsgVtbl Base classDerived class Derived vtbl class Base vtbl class Virtual functions
  • 66. Alternativa  Agrega el miembro offset a Message, de forma tal de independizar la posición relativa de esta estructura en cada una de sus derivadas 66
  • 67. Abstracciones  Supongamos que disponemos de un sistema que registra y emite la trazabilidad de una aplicación. 67 Client Tracer Client Tracer FileOut  El módulo Tracer almacena los registros de la trazabilidad en un archivo determinado, como una entidad monolítica.  Ahora, delegamos la emisión de los registros de Tracer al módulo FileOut dedicado exclusivamente a la manipulación de estos registros sobre archivos.
  • 68. Abstracciones68 Client Tracer FileOut StdOut SerialOut SocketOut  El sistema evoluciona y ahora requiere emitir los registros a diferentes medios.  Por lo tanto, modificamos Tracer para lograr el soporte necesario.  De ahora en más, un cambio en los módulos de salida puede implicar cambios en el módulo Tracer.  ¿Cómo podríamos evitar esto último?
  • 69. Abstracciones69  La mejor manera de aislar o desacoplar dos o más módulos es mediante una abstracción.  Si bien las abstracciones están fijas, representan un grupo ilimitado de posibles comportamientos.  Ahora, Tracer accede a la abstracción, y por lo tanto permanece “cerrado” para su modificación, ya que depende de una abstracción que está fija. Sin embargo, su comportamiento puede extenderse mediante la creación de nuevos módulos derivados de la abstracción.  Esto último conforma el Principio de Abierto/Cerrado (OCP). Tracer <<interface>> TracerOut FileOut StdOut SerialOut SocketOut ... Client
  • 70. Abstracciones70  El módulo de alto nivel Tracer no depende de los módulos de bajo nivel, FileOut, StdOut, etc. Ambos dependen de la abstracción TracerOut.  La abstracción no depende de los detalles de la implementación. Por el contrario, estos dependen de la abstracción.  Esto conforma el Principio de Inversión de Dependencias (DIP) Tracer <<interface>> TracerOut FileOut StdOut SerialOut SocketOut ... Client
  • 71. Vinculando la implementación con la abstracción 71  Establecida la abstracción, resta vincularla con las implementaciones. En lenguaje C, podemos utilizar varios métodos para lograrlo, básicamente:  Vinculación estática  Se produce al momento de compilar y/o linkear  Mediante el preprocesador o mediante el linker  Vinculación dinámica  Se efectúa en momento de ejecución  Mediante punteros a función  La elección de uno u otro depende de la relación de compromiso entre sus ventajas y desventajas para una aplicación determinada.
  • 72. Vinculación preprocesador72  La solución más básica consiste en una macro que provea la abstracción.  Entre otras cuestiones, todo cambio en la implementación de la abstracción provoca cambios en el código que utiliza la abstracción.
  • 73. Vinculación preprocesador73  Si afrontamos un problema de incompatibilidad entre la interfaz que necesita el cliente y la provista por el servidor, necesitamos definir un adaptador que interceda entre el cliente y el servidor.  Difícil determinar que código se compila realmente para diferentes situaciones (compilación condicional).
  • 74. Vinculación por linker74  TraceOut.h define la abstracción  Cada implementación se define en su propio archivo, donde se respeta la firma de la operación definida en la abstracción.  Se elije para linkear el archivo de la implementación requerida.  Un cambio en la implementación no provoca un cambio en el código que utiliza la abstracción.
  • 75. Vinculación dinámica Singleton 75  La implementación se vincula en tiempo de ejecución.  El módulo TraceOut administra una única instancia (singleton).  Permite obtener un único binario ejecutable para todas las implementaciones.
  • 76. Vinculación dinámica Singleton 76  Cada implementación se define en un archivo único.
  • 77. Vinculación dinámica Instancia múltiple 77  El módulo TraceOut administra múltiples instancias de un mismo tipo (clase derivada), identificadas por el parámetro me.
  • 78. Vinculación dinámica Instancia múltiple 78  A través de me, cada operación identifica la instancia sobre la cual opera.  Cada módulo se crea a partir de TraceOut mediante la herencia simple.  Dentro de cada operación se realiza un downcast para acceder a los datos de la clase derivada.
  • 79. Principios S.O.L.I.D  Cada uno de los criterios y métodos presentados se basan fundamentalmente en estos principios.  Estos forman parte de la estrategia del desarrollo ágil y adaptativo de software. 79 Inicial Acrónimo Concepto S SRP Principio de responsabilidad única (Single Responsibility Principle). Una clase debería tener una única razón para cambiar. O OCP Principio de abierto/cerrado (Open/Closed Principle). Las entidades de software (clases, módulos, funciones, etc) deben estar abiertas para su extensión, pero cerradas para su modificación. L LSP Principio de sustitución de Liskov (Liskov Substitution Principle). Los subtipos deben ser sustituibles por sus tipos bases. I ISP Principio de segregación de la interfaz (Interface Segregation Principle). la noción de que “muchas interfaces cliente específicas son mejores que una interfaz de propósito general” D DIP Principio de inversión de la dependencia (Dependency Inversion Principle). Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.
  • 80. Conclusiones  La aplicación de los principios del OOD, como el encapsulamiento, la abstracción, la herencia, y el polimorfismo, como fundamentos del diseño de embedded software en C, no es una imitación caprichosa de lenguajes orientados a objetos, sino más bien es la aplicación de ideas y conceptos maduros y formalizados, que bien empleados y en su “justa medida” permiten no sólo aumentar la productividad sino también la calidad de nuestros desarrollos.  Y así lograr software más flexible, fácil de mantener y reutilizable. 80
  • 82. Referencias [1] Leandro Francucci, “Principios de OOP aplicados en C para Embedded Systems”, Febrary, 2014, embedded-exploited.com.ar [2] Leandro Francucci, “Modularidad, abstracción y múltiples instancias en C para Embedded Software”, April, 2014, embedded-exploited.com.ar [3] Dan Saks, "Alternatives Idioms for Inheritance in C", Embedded.com, April 2, 2013. [4] Robert C. Martin, “Clean Code”, Prentice Hall, 2009 [5] Robert C. Martin, “Agile Principles Patterns and Practices in C#”, Pearson Education, 20 jul. 2006 [6] James W. Grenning, “Test Driven Development for Embedded C”, Pragmatic Bookshelf, 2011 [7] Kernighan & Ritchie, "C Programming Language (2nd Edition)", April 1, 1988. 82