A Treatise on Model Oriented Programming Languages
TL;DR · AI 摘要
A Treatise on Model Oriented Programming Languages - Stack Overflow October 9, 2026 A Treatise on Model Oriented Program...
核心要点
- 主题聚焦:A Treatise on Model Oriented Programming Languag
- 来源:Stack Overflow Blog,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
关于模型导向编程语言的论述 - Stack Overflow
2026年10月9日
关于模型导向编程语言的论述
[
十三年前,我开发了据我所知第一个也是目前唯一一个功能齐全的模型导向编程语言及集成开发环境(Mo+)。我曾成功将这项技术应用于个人及工作中的企业级项目,但未能将这一理念推广给更广泛的受众。六年前,我离开了软件工程领域,转而拿起斧头,在挪威、英国及其他斯堪的纳维亚半岛的未来地点建造维京船和盎格鲁-撒克逊船。即便如此,我偶尔仍会思考模型导向编程及其潜力。
本文的目的并非介绍我开发的技术,而是希望激发对模型导向语言的研究、开发和应用。除了其他事项外,我非常希望能为大学或企业正在进行的相关研究做出贡献。如果你了解任何有关模型导向编程语言的研究或开发,请务必告知我!
我坚信模型导向编程语言家族具有巨大的未来潜力,尤其是在当今人工智能领域,这种潜力可能尤为显著。我希望本文能激发人们更深入地探索这种潜力。
以下,我将概述一些我认为对充分发挥模型导向编程语言优势至关重要的原则和特性。除非另有说明,这些原则和特性并非纯粹的理论,而是在过去数年中实际应用于企业系统的实践。此处有许多概念需要阐述。如果前面部分显得有些枯燥乏味,至少可以跳过到语言特性部分。
什么是模型?
当然,模型有多种形式,描述模型的方式也多种多样。核心而言,模型的目的是以简洁的方式表示更大或更复杂的对象。对于模型导向编程,我们需要一个电子化的模型定义,可以使用以下定义:
模型 – 模型可以通过结构和数据进行描述。结构定义了模型的规则或“模式”,并且始终是分层的。特定模型会填充符合该模型结构的数据。
模型结构与数据
模型结构可以简单地通过节点和属性来定义:
- 模型结构具有一个单一的根节点。
- 根节点可以有任意数量的子节点。每个节点又可以有任意数量的子节点,从而形成任意深度的层次结构。
- 节点至少通过名称进行标识。此外,节点还可以通过任意数量的属性进行进一步描述。
你可能还不相信模型结构总是能以单一根节点形成层次结构。让我们以Northwind数据库的一部分作为示例:
你可能会说“这并不符合层次结构!!”。但,我们此刻看到的是什么?在我们的语境中,这不是模型结构,而是模型数据!可以说Northwind数据库模式是通过填充标准关系型数据库结构的数据示例。数据库表中的数据行是运行时数据,而不是我在此讨论的内容。模式本身是模型数据的示例。
你可以在关系型数据库的IDE中看到层次结构,因为模式可以以树状视图显示:
/think
因此,关系型数据库的模型结构包括数据库、表、列和键等节点,这些节点可以很好地组织成一个层次结构。你可能会说:“等等,这个层次结构被打破了,因为外键会引用其他表!”这个问题可以通过以下方式简单处理:
- 模型结构节点的属性可以引用模型结构中的其他节点。
通过上述规则,我们可以构建非常复杂的模型结构,并且这些结构仍然可以很好地组织成层次结构。我需要补充的是,在良好的模型结构定义中,节点应在其能够独立存在的最高层次上进行定义。
示例场景与模型
在本文的其余部分,我们将使用一个高度简化的餐厅场景作为示例,并通过多种方式对其进行建模,同时解释如何使用面向模型的编程语言来实现该场景。
简化后的餐厅场景描述如下:
- 我们需要管理多个餐厅,并跟踪它们的名称和位置。
- 一家餐厅可以有任意数量的顾客,我们希望通过姓名和到访时间来跟踪顾客(在这个简单场景中,我们暂时忽略重复顾客)。
- 一家餐厅可以有任意数量的员工,我们希望通过姓名和职位来跟踪员工。
- 顾客会食用食物,我们需要跟踪他们订购的菜单项。
- 员工为顾客和食物提供服务,我们需要跟踪他们为哪些顾客和菜单项提供服务。
以下是一个实体关系模型(采用伪Shlaer-Mellor格式)来说明这个场景:
再次强调,在我们的场景中,这个模型表示的是数据,而不是结构。现在让我们深入探讨一些模型结构!
示例模型结构
考虑以下实体关系图,它可以表示支持上述模型数据的结构:
该结构的根节点是Model,子节点包括Entity和Relationship等。实线表示模型结构的层次关系,而虚线表示交替关联(注意,对于这些关联,我需要添加一些引用属性来“形式化”这些关联)。在填充此结构时,Entity的实例包括Restaurant、Customer、Staff等,而Restaurant实体属性的实例包括Id、Name和Location等。
这是否是表示我们数据的唯一或最佳结构?当然不是,存在许多可能性。考虑以下替代的实体-关系模型结构:
该结构将Entity、Relationship和Property放在同一层级。如果关系和属性可以独立于实体存在,这可能更有意义。
最后考虑这种替代的实体关系模型结构:
在此结构中,关系和属性需要依赖实体才能存在,关系是其源实体的子节点。我之所以提到这些替代结构,是因为这会对面向模型的编程语言产生一定影响,其中层次结构在遍历时会更加方便。这将在后面详细解释。
模型总结
我坚信,从结构和数据角度定义模型是一个极其重要的基础性原则,它为面向模型的编程语言家族打开了大门。随着我们继续深入探讨,我会进一步解释这一点。
在继续之前,这里还有一些定义:
建模 – 建模可以简单地定义为用数据填充模型结构的过程。
面向模型编程(MOP) – MOP 可以简单定义为利用模型来创建和维护系统的过程(本文中系统特指软件系统)。
面向模型开发(MOD) – MOD 可以简单定义为创建并利用模型来构建和维护系统的过程。换句话说,MOD 包含建模和 MOP 两个阶段。
为了方便表述,本文后续将使用 MOP 和 MOD 这两个缩写。
面向模型开发(MOD)的形式
据我所知,目前没有对 MOD 不同形式的标准命名,但我认为描述这些形式及其对 MOP 的影响是有价值的。
非正式 MOD
非正式 MOD 涉及使用任何非标准形式的模型。模型可以是脑海中的想法、纸张或白板上的随手涂鸦,或是使用绘图工具创建的图形。创建和使用非正式模型没有规则约束。
从我们的角度出发,非正式 MOD 可以大致定义为:
- 在源代码之外拥有临时的模型定义。这些模型可能仅仅是脑海中的想法、文字描述、自行设计或标准的图纸和图表。
- 没有任何软件工具能够利用你的模型来管理源代码。
以下是一张白板草图(与上文重复),它展示了我们餐厅场景的基本概念模型。稍后我们将讨论该场景的潜在模型结构:
内联 MOD
随着众多框架的出现和使用,内联 MOD 形式如今非常流行,这些框架极大地帮助开发者高效设计和构建应用程序。
内联 MOD 可以大致定义为:
- 在源代码中定义正式的模型。该模型通常与应用程序的某一层相关。
- 拥有配套框架,通常具备支持基础设施,允许你管理并使用模型、自动生成部分代码,或提供基础设施软件来协助管理系统部分功能。
内联 MOD 的许多示例包括 ORM 框架(如 Entity Framework 或 NHibernate)和 UI 框架(如 Angular 和 React)。
内联 MOD 的模型通常在代码中定义。以下是我们餐厅场景的 Entity Framework(代码优先)模型定义示例:
public class Restaurant
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public ICollection<Customer> Customers { get; set; }
public ICollection<Staff> Staff { get; set; }
}
public class Staff
{
public int Id { get; set; }
public string Name { get; set; }
public string Role { get; set; }
public ICollection<Customer> CustomersServed { get; set; }
public ICollection<Food> FoodServed { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime VisitedDate { get; set; }
public ICollection<Food> FoodEaten { get; set; }
}
public class Food
{
public int Id { get; set; }
public string MenuItemName { get; set; }
public string Ingredients { get; set; }
}上述任何实体关系模型结构都可以用于管理上述数据,也可以使用包含 Table、Column 等节点的特定结构,例如以下简化结构:
耦合 MOD
标准的 UML 建模语言以及支持 UML 建模和源代码管理的工具,是耦合式 MOD 形式最常见的示例。
耦合式 MOD 可以大致定义为:
- 在源代码之外定义形式化模型。
- 模型元素与源代码之间存在强关联(通常是 1:1 关系)。因此,模型包含了软件设计的元素。
- 存在可以管理模型并利用该模型管理源代码的软件工具。
许多 UML 图表的目的是阐明系统设计。以下是我们餐厅场景的类模型图示:
我们可以使用通用的实体-关系模型结构来支持类模型数据,但对于这种 MOD 形式,我们可能更倾向于使用以下更具设计导向的结构来表示类模型:
解耦式 MOD
随着 UML 在设计规范中的使用增加,我认为解耦式 MOD 形式在业界整体上受到的关注较少。解耦式 MOD 可以大致定义为:
- 模型元素与源代码之间存在弱关联。源代码与模型解耦。抽象模型通常几乎不包含任何设计信息。详细设计也与模型解耦。
- 存在可以利用模型管理源代码的软件工具。
识别这种形式的关键在于模型的目的是阐明思想和需求。与建模到设计或组件级别不同,建模通常仅限于需求,主要以数据和工作流为主。我倾向于使用早期的 Shlaer-Mellor 方法对数据和工作流进行建模。
以下是我们的餐厅场景的信息模型(重复自上方):
在解耦式 MOD 中,模型中的元素(如 Restaurant)不会指向特定组件,而是概述了多个相关组件(如桌子、类、Web 服务、UI 组件等)的需求。
通用的实体-关系模型结构与解耦式 MOD 配合良好。随着下面编码示例的展开,我将使用以下结构(重复自上方):
MOD 形式对 MOP 语言的潜力
能否开发一种 MOP 语言来支持上述任何一种 MOD 形式?可以!能否开发一种 MOP 语言来支持上述所有 MOD 形式?尽管听起来可能有些疯狂,但答案是肯定的!话虽如此,我认为 MOP 语言的潜力按以下顺序从低到高排列:
非正式 MOD 的潜力
MOP 语言有潜力支持非正式 MOD。主要问题是能够以电子形式表示非正式模型,使语言解释器能够理解其结构和数据。MOP 语言确实可以理解和利用完全非标准的结构!我将在下面解释如何实现这一点。支持非正式模型的收益相对较低,可能仅对特定工作场所或团队相关。
内联 MOD 的潜力
在涉及ORM和UI框架等框架时,这些框架通常利用标准的模型结构来完成其工作。因此,完全可以开发一种MOP语言,利用这些模型进一步开发此类框架及其应用。该语言的适用范围可能仅限于框架本身的范围。此外,开发框架的组织可能希望MOP语言仅用于内部开发目的。
作为补充说明,几年前我曾在一定程度上为Entity Framework和VITA ORM实现了类似功能,相关内容可参见我发表在Code Project上的部分文章。
耦合MOD的潜力
借助UML等标准化行业模型结构以及大量用于管理和利用耦合MOD模型的工具,MOP语言在此类MOD形式中具有广泛应用潜力。此类语言可以直接从UML模型创建和维护系统。现有的模型与代码相互转换工具也可以通过MOP语言实现功能扩展。
解耦MOD的潜力
我认为MOP语言在支持解耦MOD方面的潜力甚至超过耦合MOD。除了带来诸多行业性优势外,将设计更多地纳入代码领域、将需求更多地纳入模型领域,可以使建模过程和编码过程更加高效流畅。至少,这一直是我个人的实践经验。
模型导向编程(MOP)的应用场景
在讨论MOP语言的潜在功能之前,描述MOP可以应用的不同场景是有益的,因为这些场景对MOP语言的需求和能力会产生不同的影响。
请考虑下图,该图展示了建模领域和系统编程领域的范围,以及MOP可能应用的不同位置:
过渡性MOP
过渡性MOP的目的是解释和利用模型结构及数据,以创建和维护目标系统环境。目标系统环境可以包括任何语言的源代码、文档、配置文件等。假设目标环境将通过在目标环境中直接进行额外编程等方式进一步增强。
在这种情况下,MOP语言不一定是目标环境中使用的语言,但需要支持目标环境中使用的语言和其他文档标准。以下对MOP语言特性的讨论将从这种情形开始。
建模MOP
建模MOP的目的是帮助创建和维护模型的结构和数据。创建和维护模型所需的信息可能来自系统本身(如源代码),也可能来自其他外部来源(如模型、数据库、文档等)。
支持建模的MOP语言可能需要具备其他情形中未使用的某些特性。
目标MOP
目标MOP的目的是直接创建和维护目标系统环境。在这种情况下,MOP语言将直接在目标环境中使用,并可能需要比其他情形中更“功能丰富”。
过渡性MOP的MOP语言特性
与面向对象语言的对比
以上内容为讨论MOP语言所做的铺垫,希望你仍然在关注!现在我们可以讨论MOP语言的各种潜在特性。我将从高层次对比面向对象语言的差异,以进一步说明MOP语言的潜在优势。
我们现在开始通过一些简单的代码示例进行说明。我们将使用以下颜色进行语法高亮:
- 核心语言语法(如语句)将以蓝色呈现。
- 模型结构语法将以青色呈现。
- 模型导向属性引用将以棕色呈现。
- 输出内容将以绿色呈现。
顺便说明,下面呈现的语言语法可能与我实现的Mo+语言相似或不同。此处展示的语法是为了清晰度而设计的。
编程会话
对于功能完备的MOP语言,我认为设置编程会话的概念是必要且关键的。要启动一个会话,MOP语言解释器需要解析并加载模型结构,然后使用可用的模型数据加载该结构,从而创建模型快照:
编程会话 – 模型结构已知且模型数据为只读状态的时间段。在会话范围内进行操作(无论会话长短),可以启用多种MOP语言特性。
将模型结构作为语言语法使用
考虑到模型结构在运行时是分层且已知的,并为了充分利用这种结构,将该结构作为语言语法的一部分是非常有用的!因此,使用我们之前的实体关系示例模型,通过将结构纳入语法,你可以直接对实体、关系、属性等进行操作。对于面向对象语言,你需要为实体、关系、属性等创建结构或类,并实例化它们才能获得可操作的模型结构。当我们开始使用这种语法时,这一点会更加清晰。
模型结构作为语法 – 将分层模型结构的节点和属性纳入语言语法非常有用。将模型结构添加到模型语法中,无需创建和实例化特殊类或结构即可更简单地编写模型代码。
将模型数据作为模型上下文使用
考虑到模型数据在运行时是只读的且符合分层结构,你可以想象遍历模型数据树以访问任何所需内容非常容易。将模型上下文的概念添加到语言语法中是非常有用的。与面向对象语言中的对象上下文不同,无需创建特定变量或实例来保存上下文,你只需要一个指向模型数据树中节点的引用即可。
使用我们解耦的MOD实体-关系结构,这是餐厅场景模型数据的树状视图:
Model: Name=My Model
Entity: Name=Restaurant
Property list: {Id, Name, Location}
Relationship list: {Hires(Staff), Has(Customer)}
Entity: Name=Staff
Property list: {Id, Name, Role}
Relationship list: {Serves(Customer), Serves to Customers(Food)}
Entity: Name=Customer
Property list: {Id, Name, VisitedDate}
Relationship list: {Eats(Food)}
Entity: Name=Food
Property list: {Id, MenuItemName, Ingredients}假设模型上下文引用的是 Restaurant 实体下的 Location 属性。考虑以下语句:
print(Entity.Name, “.”, Name)
该语句的输出结果为 Restaurant.Location。这是如何实现的?由于模型结构是语法的一部分,且模型上下文位于 Location 属性上,Name 会返回 Location。由于结构/数据层级关系,可以轻松获取树上任意位置的数据,因此 Entity.Name 返回 Restaurant。再进一步:
print(Model.Name, “: “, Entity.Name, “.”, Name)
输出结果为 My Model: Restaurant.Location。现在我们来看看如何访问子数据并进行更复杂的操作?
此外,将模型上下文实现为堆栈有助于以多种方式访问整个模型树。
现在假设模型上下文引用的是 Restaurant 实体。考虑以下语句:
foreach (Property)
{
print(Entity.Name, “.”, Name)
}
foreach (Relationship)
{
print(Entity.Name, “.”, Name, “:”, DestinationEntity.Name)
}这将产生以下输出:
Restaurant.Id
Restaurant.Name
Restaurant.Location
Restaurant.Hires:Staff
Restaurant.Has:Customer这里发生了什么?由于模型结构是语法的一部分,也可以访问子节点。foreach 语句允许您遍历指定类型的子节点。请注意 foreach 语句中没有变量。相反,在迭代开始时,集合中的项目会被推送到模型上下文堆栈,迭代语句结束时会被弹出。例如,在 foreach (Property) 中,堆栈底部的当前模型上下文是集合中的当前 Property。如前所述,您可以轻松访问树上层的数据。
除了这些,您还能用模型上下文堆栈做些什么?以下语句将产生相同的结果:
foreach (Property)
{
print(../Name, “.”, Name)
}
foreach (Relationship)
{
print(../Name, “.”, Name, “:”, DestinationEntity.Name)
}可以使用 ../、../ ../ 等符号访问堆栈上层的模型上下文元素,这在某些情况下非常有用。在此示例中,我们使用这种符号来获取 Restaurant 实体的模型上下文。
还可以轻松访问模型层级中任意层级的子节点:
foreach (Property in Model orderby Entity.Name, Property.Name)
{
print(Entity.Name, “.”, Name)
}这将检索整个模型中所有属性的排序列表,并产生以下输出:
Customer.Id
Customer.Name
Customer.VisitedDate
Food.Id
Food.Ingredients
Food.MenuItemName
Staff.Id
Staff.Name
Staff.Role我们再通过一个包含搜索的模型上下文示例来演示。考虑以下语句:
with (Entity in Model where Name=”Food”)
{
foreach (Relationship in Model where DestinationEntity.Name=”Customer”)
{
print(Entity.Name, “.”, Name, “:”, ../Name)
}
}这个嵌套搜索将产生以下输出:
Restaurant.Has:Food
Server.Serves:Food最后一个示例有些牵强,我确信使用模型上下文堆栈可以更优雅地实现更复杂的操作,但希望这些示例能帮助您了解模型上下文可以实现的多种可能性。
模型上下文 – 将模型上下文的概念纳入语言语法中是非常有用的。模型上下文提供了一种从树中的特定位置访问模型数据的方法。特别是如果将其实现为栈结构,可以更好地支持模型数据的浏览和搜索,这样无需实例化特殊上下文变量,就能更简便地访问整体模型数据。
面向模型的属性
我认为这正是MOP语言真正有趣的地方。当模型结构作为语法时,我提到过可以直接对实体、关系、属性等“执行操作”。而通过模型上下文,你可以随时访问模型数据的树结构。所有这些特性最终汇聚到面向模型的属性上!
与面向对象属性或类定义中的一部分成员不同,当模型结构作为语法时,面向模型的属性会成为更加独立的代码片段。
首先,每个面向模型的属性都与模型结构中的特定节点相关联。考虑以下属性:
Entity property PublicClassDeclaration
get
{
print( “public class “, Name)
}这个面向模型的属性只有在给定Entity实例时才能正常工作。如果传入Staff实例,输出结果为:
public class Staff
在本文中,我将使用print和println在属性get方法中构建内容,这些内容随后会被返回。现在让我们定义一个类似的属性级别的面向模型属性:
Property property PublicPropertyDeclaration
get
{
print( “public “, Datatype, “ “, Name, “ {get; set;}”)
}这个面向模型的属性只有在给定Property实例时才能正常工作。如果传入Staff.Role实例,输出结果为:
public string Role {get; set;}
面向模型的属性可以利用其他面向模型的属性。考虑以下示例:
Entity property PublicClassCode
get
{
println( PublicClassDeclaration)
println(“{”)
foreach (Property)
{
println(“\t”, PublicPropertyDeclaration)
}
println(“}”)
}如果传入Staff实例,输出结果为:
public class Staff
{
public int Id {get; set}
public string Name {get; set}
public string Role {get; set}
}最后,由于面向模型的属性是独立的代码片段,它们可以以多种方式使用,例如作为表达式的一部分:
foreach (Entity in Model where PublicClassCode.Contains(“Role”) == true)
{
print(Name)
}这个简单的示例会查找所有类代码中包含“Role”的实体,从而找到Staff实体:
Staff
面向模型的属性还可以有哪些其他方法?在模型被锁定在会话中的过渡性MOP中,为更新模型设置set方法是没有意义的。但put方法可以用于在目标系统中添加和更新信息。这里,我们为PublicClassCode方法添加了一个put方法:
Entity property PublicClassCode
get
{
println( PublicClassDeclaration)
println(“{”)
foreach (Property)
{
println(“\t”, PublicPropertyDeclaration)
}
println(“}”)
}
put
{
save(content, PublicClassFilePath)
}put方法会将类代码保存到由PublicClassFilePath属性指定的文件位置。content关键字是获取get方法结果的简写方式。
要创建或维护一个系统,本质上可以选择一组面向模型的属性作为“容器”,以满足该系统的技术需求。程序运行时会从顶层(模型)属性开始,该属性会依次启动高级组件(项目)属性,而这些属性又会启动更底层的属性,依此类推。
通过能够在不实例化类或容器的情况下定义和使用面向模型的属性,能够将面向模型的属性作为构建更复杂属性的基石,以及能够在表达式中自由使用面向模型的属性,希望你已经理解了这些属性的多功能性。
面向模型的属性 – 面向模型的属性是与模型结构中节点(如实体)相关联的一段独立代码。在运行时,当给定与模型上下文相同的节点(如实体)实例时,面向模型的属性可以被评估。面向模型的属性可以使用相同节点的不同实例进行多次评估,并且可以以比面向对象属性更多的方式被使用。
动态语法
一种编程语言的动态语法是指可以在运行时改变或扩展的语法。其他编程语言是否有动态语法?
如果模型结构是分层的,并且是语言语法的一部分,那么对于MOP语言来说,拥有动态语法是可行且实际有用的。实际上,对于MOP语言解释器来说,实现一个语法构建器来读取模型结构并在会话开始时将其添加到语法中是非常直接的。这只需要将节点和属性添加到模型层次语法中。我将在下面的“建模MOP”部分中介绍一些扩展模型层次的示例。
那么,为什么动态语法会很有用?我将给出几个例子。
模型信息不足
即使使用了标准化模型,我经常发现标准模型有时缺少一些有效编程所需的信息。下面会更详细地描述一种通过标签形式向节点添加数据的低成本方式,但有时这并不足够,我们可能需要向节点添加更多属性,甚至向模型结构中添加新节点。
在我们的情况下,我们决定需要在模型结构中的Property节点添加MaxLength属性。当语法构建器识别并添加此属性到语法中后,它就可以像Property的任何其他属性一样被使用。
以下是新属性和更新后的类代码属性,以利用MaxLength:
Property property StringLengthAttribute
get
{
if ( MaxLength > 0)
{
print(“ [StringLength(“, MaxLength, “)]”)
}
}
Entity property PublicClassCode
get
{
println( PublicClassDeclaration)
println(“{”)
foreach (Property)
{
if ( StringLengthAttribute != “”)
{
println(“\t”, StringLengthAttribute)
}
println(“\t”, PublicPropertyDeclaration)
}
println(“}”)
}
put
{
save(content, PublicClassFilePath)
}Staff的更新后类代码如下(仅在Name上设置MaxLength):
public class Staff
{
public int Id {get; set}
[StringLength(255)]
public string Name {get; set}
public string Role {get; set}
}非标准模型
如果模型结构部分或完全非标准,或者特定于某个组织或行业,我们将需要对整个模型结构进行解释并构建语法。同样,语法构建器可以轻松完成这一任务。
非正式 MOD 的模型将是非标准的,而拥有动态语法来加载非正式模型结构和数据,是有效支持非正式 MOD 的唯一方式。
让我们尝试完全不同的方法。使用上面餐厅场景的非正式示意图,这里有一个更字面的模型结构:
如果我们在构建企业级应用程序时使用这种结构,可能不会很有意义。但如果是这种结构,数据会是什么?它将与运行时数据完全相同!因此,实际上我们的语言可以成为一种查询语言。以下是一个查询示例:
foreach (Restaurant)
{
println(Name, “: ”, Location)
foreach (Staff)
{
println(“\tStaff: ”, Name, “, ”, Role)
}
foreach (Customer)
{
println(“\tCustomer: ”, Name)
}
}一些查询输出可能如下所示:
Chez Food: London
Staff: Verity Jones, Chef
Staff: Nigel Walker, Server
Customer: David Spencer
Customer: Catherine Rees
Chez Food: Paris
Staff: Monique Dubois, Chef
Staff: Henrique Bernard, Server
Staff: Michelle Fontaine, Server
Customer: Pierre Simon
Customer: Camille Fournier我尚未尝试通过读取字面结构来执行查询,因此这只是纯粹的推测。将大量运行时数据加载到会话中并不实际。
或许在模型结构中包含非常具体的元素(如 Restaurant)的另一个原因,是允许您为与其它元素差异较大的元素编写非常具体的面向模型的代码。在实践中,我只发现需要通用结构的必要性。
语法构建器 – 一种语言解释机制,可在运行时修改或增强语言语法。MOP 语言解释器可以读取模型结构,并在会话开始时将该结构添加到语法中。
上下文语法
虽然不如动态语法重要,但我认为上下文语法的概念对 MOP 语言非常有用,尤其是因为 MOP 可以应用于不同场景,例如支持建模、从模型转换以及目标编程。与动态语法一样,MOP 语言解释器实现语法过滤器以根据上下文提供适当的语法,从而帮助防止可能导致意外后果的代码编写,这非常直接。
全局范围
对于某些面向模型的属性方法,我认为限制可编写代码的能力是有用的,这有助于划分责任并防止意外后果。我们可能需要以下过滤器:
- 在面向模型的属性获取方法中,禁止以任何方式将信息写入其他位置。过滤语言语法以防止可以将数据写入任何地方的语句。
- 在面向模型的属性设置方法中,允许将信息写入模型以外的其他位置。过滤语言语法以防止可以将数据写入模型以外的语句。
- 在面向模型的属性放置方法中,仅允许将信息写入模型。过滤语言语法以防止可以将数据写入模型的语句。
过渡性 MOP 过滤器
为了使编程能够利用模型来管理目标环境,我们可能需要以下过滤器:
- 抑制在用于更新模型和使编程会话失效的模型导向属性中创建set方法的能力。
建模MOP过滤器
为了让编程能够管理模型,我们可能需要以下过滤器:
- 抑制在用于更新模型外部信息的模型导向属性中创建put方法的能力。
目标MOP过滤器
为了让编程能够直接管理目标环境,可能需要更健壮的语法来支持目标程序员可能需要执行的任何操作。在这种情况下,可能希望允许所有类型的模型导向属性方法,并通过适当的语法过滤器进行控制,同时认识到set方法可能会使编程会话失效。
语法过滤器 – 一种语言解释器机制,根据上下文过滤整体语言语法,帮助防止可能导致意外后果的代码编写。
几乎不需要变量!
过渡性MOP只需要极少的变量!通过模型上下文和对整个模型数据树的访问,有很多情况下不需要使用变量,而其他语言中却需要变量:
- 在搜索或浏览时,不需要变量来保存迭代过程。
- 无需变量来评估模型导向属性。
- 模型导向属性可以被用在许多不同的地方,例如作为表达式的一部分。
事实上,我是在开发过程的后期才实现对变量的支持。对于过渡性MOP来说,除了少量全局(状态)设置外,我最多只在约1%的模型导向属性中使用了变量,而这些情况可能都可以通过更好的模型导向编程或稍微增强模型结构来处理。我发现几乎总是可以通过编写模型导向属性来表达在其他语言中需要使用变量实现的功能。
极大的可重用性
如上所述,模型导向属性非常灵活,是几乎可以用于任何地方的独立代码片段。这种灵活性使得在程序内部和跨程序中都能实现极大的可重用性。在实践中我发现,模型导向属性通常本质上是超越特定项目功能需求的最佳实践编码。
以下是一个非常简单的类命名约定示例,可以重复使用数百次。当然,更复杂的"最佳实践"属性也可以被反复复用。
Entity property ClassName
get
{
print( Name.PascalCase())
}极大的并行性
如果几乎不需要变量且模型数据在编程会话中被密封为只读,可以推断出在评估模型导向属性时存在实现极大并行性的潜力。
我没有实现并行处理,但实现了缓存机制。解释器会为特定模型上下文(节点实例)缓存评估模型导向属性的某些结果,以便后续多次使用这些结果。仅通过实现这一机制,我就获得了显著的性能提升。
与目标语言(自定义)代码的集成
我的使用 Transitional MOP 的经验是,通过合理努力,你可以将目标企业环境的 80% 管理到最佳实践水平,如果投入两倍或更多的努力,可能可以达到 90%。无论如何,你需要有一个方法,能够将目标环境 100% 维护到最佳实践,尤其是在使用 Transitional MOP 进行更新时。
这是一个需要更多思考和复杂度处理的领域。以下方法都不是理想方案,但可以尝试:
- 部分文件 – 最简单的起点是让 MOP 语言解释器创建和更新的文件与目标语言直接创建或修改的文件保持分离。部分类文件就是这种做法的示例。
- 定制化点 – 部分文件通常只能有限支持定制化。解释器管理的文件中应能定义灵活的定制化点(如命名区域或注释)。这样可以在这些点添加自定义代码,当文件需要整体更新时,解释器可以重新应用这些自定义代码。
- 文件更新和删除策略 – 解释器应仅更新和删除它管理的文件,对其他自定义文件保持原样。此外,如果管理的文件中包含任何自定义代码,这些代码不应被删除。
- 忽略策略 – 最后,如果某些由解释器管理的文件需要大量定制化,可以将其标记为忽略,这样解释器在将来会完全忽略这些文件。
按 MOD 形式的影响
上述所有功能都可以在 MOD 的各种形式中使用,但可能存在一些注意事项:
编程会话 – 使用耦合式 MOD 时,编程会话可能会更频繁地过时,因为设计变更和增强非常频繁。此外,由于详细设计模型比其他形式的模型大得多,加载模型数据需要更多内存。
动态语法 – 对于非正式 MOD,动态语法和语法构建器将是必需的,因为没有统一的模型结构标准。
大规模复用 – 这更多是一种直觉而非实证数据,但我认为在解耦式 MOD 中使用时,其可用性会更高。使用这种形式时,我发现我的大多数面向模型的属性本质上都是可以跨多个项目多次复用的最佳实践编码。
对建模 MOP 的影响
由于建模 MOP 的目标是创建和维护模型的结构和数据,因此 MOP 语言的一些需求与 Transitional MOP 不同。当然,可以通过 IDE 手动创建和维护模型结构和数据,但以下功能有助于实现编程化建模:
定义模型结构
我认为能够定义或增强模型结构是 MOP 语言的关键功能,以应对多种编程场景。基本功能包括在分层模型结构中添加和编辑节点,以及为这些节点添加和编辑属性。反过来,这些信息可以传递给语法构建器,以将结构添加到模型语法中。
以下是一个简单的语句示例,用于创建以 Restaurant 为根节点的餐厅模型结构:
/restaurant
/name
/location
/menu
/item
/name
/pricecreateModel (“Restaurant”)
addNode (Restaurant, “Customer”)
addNode (Restaurant, “Staff”)
addNode (Restaurant, “Food”)
addAttribute (Restaurant, “Id”)
addAttribute (Restaurant, “Name”)
addAttribute (Restaurant, “Location”)
addAttribute (Staff, “Id”)
addAttribute (Staff, “Name”)
addAttribute (Staff, “Role”)
addAttribute (Customer, “Id”)
addAttribute (Customer, “Name”)
addAttribute (Customer, “VisitedDate”)
addAttribute (Food, “Id”)
addAttribute (Food, “MenuItemName”)
addAttribute (Food, “Ingredients”)请注意,添加或编辑模型结构的语句可以立即通过语法构建器更新语法,使新模型元素能够立即使用。一个完整的模型结构管理语句集将包含更多功能,例如编辑和删除,以及更多信息,如数据类型等。
导入/导出
支持建模MOP的重要功能之一是能够与其他模型数据源进行导入/导出。一些示例包括:
- XML文档 – XML导入/导出需要能够确定输入XML的结构,以及XML中的节点和属性如何对应到正在使用的模型结构,以便执行模型数据的导入。
- 数据库 – 数据库导入需要能够为支持的数据库类型设置连接字符串,能够读取数据库的模式,并确定模式元素如何对应到正在使用的模型结构,以便执行模型数据的导入。
- 其他模型 – 类似于上述示例,模型导入/导出需要能够理解对应的模型结构,以便执行模型数据的导入或导出。
在实际应用中,我大部分企业项目的建模工作都使用了从其他模型源导入数据。如果你同时进行手动建模和导入,IDE需要能够跟踪并维护模型在后续导入模型数据时的手动更改。
标记
标记是一种通过向模型数据树中的任何元素添加非结构化数据来指定额外需求的简单方式。这些标记可以只是一个字符串值列表,或者作为名称/值对附加到模型元素上,以区分其他元素。
例如,假设员工姓名是机密信息,我们有一个属性用于加密该属性。我们可以向“员工姓名”属性添加一个标记,如“secure”,该标记可用于特殊处理“员工姓名”属性:
Entity property PublicClassCode
get
{
println( PublicClassDeclaration)
println(“{”)
foreach (Property)
{
if (Tags.Contains(“secure”))
{
println(“\t[Encrpyt]”)
}
println(“\t”, PublicPropertyDeclaration)
}
println(“}”)
}Staff类的代码如下所示:
{
public int Id {get; set}
[Encrypt]
public string Name {get; set}
public string Role {get; set}
}对目标MOP的影响
由于我没有将MOP语言实现为目标语言,因此本节内容纯属我的推测,将是持续研究的主要领域。
语言语法
目标MOP语言无疑需要比过渡MOP更广泛的语法,以处理目标语言程序员需要执行的所有操作。除了其他因素外,类型安全性对于过渡MOP不是必需的,但对于目标MOP可能必不可少。
与过渡MOP的集成
我认为这是一个非常有趣的领域,需要深入思考如何管理这种集成。我之前提到过,使用过渡型MOP(Transitional MOP)时,可以通过定义定制点来管理并维护自定义代码。但如果是真正的目标MOP语言,我认为我们需要比这更智能的解决方案。
让我们设想一下,如果使用过渡型MOP来利用模型并构建和维护项目的基本或高度复杂的企业框架,你将如何将目标代码添加到这个框架中?如果基于过渡型MOP(最佳实践或其他变更)对框架进行了更新,你又将如何管理目标代码的变更?我目前没有具体的方案,但可以想象一个智能的IDE,它能让程序员对比框架更新与目标代码的差异,并协助完成目标代码的更新过程。你怎么看?
### 与建模MOP的集成
建模MOP或手动更新模型数据对目标编程的影响与上述情况相同。当会话和框架更新时,我认为同样需要一个智能且图形化的IDE来帮助管理目标代码的变更。
那么,当我们手动或通过建模MOP更改模型结构时会发生什么呢?这将改变MOP语言的语法!这将如何管理?嗯……!!
使用过渡型MOP时,删除模型结构元素会产生可解决的编译错误,而添加模型结构元素只会用于面向模型的属性变更。
当语言语法发生变化时,为了管理目标代码,我认为同样需要一个智能的IDE来处理。具体如何实现,我目前也没有很好的想法。你怎么看?
### 变量
作为目标语言,显然需要更多的变量来处理各种用户或其他I/O操作。即使如此,我认为变量的使用频率将远低于其他语言。
### 新语言还是扩展面向对象语言?
作为目标语言,我认为MOP语言与面向对象语言在语法上可能存在显著重叠。因此,MOP语言作为目标语言,应该是一个独立的语言,还是对现有面向对象语言的增强?值得思考!
## 总结
我希望这篇文章能激发大家对面向模型语言潜力的探索。这里有很多概念需要消化,请查看并告诉我我可以在哪些方面让内容更清晰,以及是否有相关的研究工作可以参考。我将很乐意根据反馈更新这篇文章。
几年前,我在codeproject.com上写过几篇关于面向模型语言和开发的文章。希望该网站未来能重新活跃起来。
如果你想探索我多年前开发的语言和IDE Mo+,它仍然可以在 https://github.com/moplus 上找到。它最后一次作为Visual Studio插件发布于2014年,但源代码和独立安装程序仍然可用。IDE包含完整的帮助系统和在线参考,可以尝试使用该语言。项目包含模板(面向模型属性的软件包),你可以使用这些模板创建一些示例项目。
这些文章采用知识共享署名-相同方式共享4.0国际许可协议进行授权。
creativecommons.org/licenses/by-sa/4.0/deed.en