[ Team LiB ] |
1.4 ComponentsWhatever language we build our software in, we end up creating executable files that are loaded and run by the operating system. In days gone by, software was monolithic in nature—all the code and data required for an application was compiled and linked into a single executable lump of code. While there may be a certain elegant simplicity to this approach, it did little to encourage code reuse.[1] It also tended to encourage a programming style sometimes referred to as the "big ball of mud," where any individual part of the code is messily intermingled with lots of other parts, and there is no overall structure to the code. This was not especially conducive to code quality or developer productivity, and in extreme cases, a software project could become so entangled and intractable that fixing one bug could easily introduce several more bugs due to the unforeseen side effects of the change. The object-oriented (OO) features of C++ were not a sure-fire solution to this problem, because unless developers were scrupulous about encapsulating their code and keeping classes independent of each other, all the same problems could emerge in an OO program.
Component-based software development was one of the most significant advances in software engineering to be adopted over the last decade. Componentized applications are not monolithic—they are broken down into discrete chunks (or components) with clear roles and well-defined boundaries. A key feature is that software components are binaries (i.e., compiled executables rather than collections of source code). This has the effect of preventing unrelated parts of the system from gradually merging just because of expediency—it means there are always clear divisions between parts of the system. This is particularly true if the individual components are developed by different groups: if there are any structural problems with the code, these must be dealt with by fixing the problems rather than resorting to hacks to work around them. Of course, it is possible to write bad code in any programming system, and .NET doesn't change that. As always, there is no silver bullet. But with component-based systems like .NET, developers have to go out of their way to make one component depend on internal features of another component, so at least it encourages better practice, even if it can't enforce it. For a component system to be workable, it must define two things: what constitutes a component and how components communicate with one another. Prior to .NET, the Component Object Model (COM) provided both definitions. Components were DLLs (or occasionally EXEs) with certain standard entry points, and that normally had type information attached. They communicated with each other by adhering to COM's programming model. .NET replaces these definitions with assemblies and the CLR, respectively. An assembly is usually a DLL or EXE file, and it contains type definitions, along with any code and data for those types. In .NET, type definitions (and therefore all associated code) always live inside an assembly. Assemblies define the physical representation of a component in .NET. As with COM, they still rely on the same PE file format used by all executables in Windows, but they extend it to provide much more type information than was previously available. COM's type information provided no way of determining which other types a particular component depended upon, making it hard to be certain which components needed to be deployed to form a complete working system. In .NET, this is no longer a problem, because all assemblies list not only the types that they define but also all of the externally defined types that they use and the components in which those types are defined. |
[ Team LiB ] |