封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。
抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象。
C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
封装意味着对另一个类隐藏信息,但是封装中的信息可以使用反射拿出来
SwordDamage游戏
本项目是一个计算伤害的程序
第一版
xml
<Window x:Class="SwordDamage_WPF_Part_1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SwordDamage_WPF_Part_1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="flaming" Content="Flaming"
HorizontalAlignment="Center" VerticalAlignment="Center"
Checked="Flaming_Checked" Unchecked="Flaming_Unchecked"/>
<CheckBox x:Name="magic" Content="Magic" Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Center"
Checked="Magic_Checked" Unchecked="Magic_Unchecked" />
<Button Grid.Row="1" Grid.ColumnSpan="2" Margin="20,10"
Content="Roll for damage" Click="Button_Click"/>
<TextBlock x:Name="damage" Grid.Row="2" Grid.ColumnSpan="2" Text="damage"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
下列是SwordDamage类
包含有基础伤害和火焰伤害,还有伤害倍率
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SwordDamage_WPF_Part_1
{
class SwordDamage
{
public const int BASE_DAMAGE = 3;
public const int FLAME_DAMAGE = 2;
public int Roll;
public decimal MagicMultiplier = 1M;
public decimal FlameMultiplier = 0;
public int Damage;
public void CalculateDamage()
{
Damage = (int)(Roll * MagicMultiplier) + BASE_DAMAGE + FLAME_DAMAGE;
}
public void SetMagic(bool isMagic)
{
if (isMagic)
{
MagicMultiplier = 1.75M;
}
else
{
MagicMultiplier = 1M;
}
CalculateDamage();
}
public void SetFlaming(bool isFlaming)
{
CalculateDamage();
if (isFlaming)
{
Damage += FLAME_DAMAGE;
}
}
}
}
以下是游戏的逻辑关系,是有一定的Bug,通过思考错误原因,提升自己的能力
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SwordDamage_WPF_Part_1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
///
public partial class MainWindow : Window
{
Random random = new Random();
SwordDamage swordDamage = new SwordDamage();
public MainWindow()
{
InitializeComponent();
swordDamage.SetMagic(false);
swordDamage.SetFlaming(false);
RollDice();
}
public void RollDice()
{
swordDamage.Roll = random.Next(1, 7) + random.Next(1, 7) + random.Next(1, 7);
DisplayDamage();
}
public void DisplayDamage()
{
damage.Text = "Rolled " + swordDamage.Roll + " for " + swordDamage.Damage + " HP"; ;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
RollDice();
}
private void Flaming_Checked(object sender, RoutedEventArgs e)
{
swordDamage.SetFlaming(true);
DisplayDamage();
}
private void Flaming_Unchecked(object sender, RoutedEventArgs e)
{
swordDamage.SetFlaming(false);
DisplayDamage();
}
private void Magic_Checked(object sender, RoutedEventArgs e)
{
swordDamage.SetMagic(true);
DisplayDamage();
}
private void Magic_Unchecked(object sender, RoutedEventArgs e)
{
swordDamage.SetMagic(false);
DisplayDamage();
}
}
}
该程序在我们点击Roll for damage时,伤害没有发生改变
观察代码可得在我们点击按钮后,调用Button_Click函数,没有从新计算数值
可以在计算时加上
csharp
public void RollDice()
{
swordDamage.Roll = random.Next(1, 7) + random.Next(1, 7) + random.Next(1, 7);
swordDamage.SetFlaming(flaming.IsChecked.Value);
swordDamage.SetMagic(magic.IsChecked.Value);
DisplayDamage();
}
但是火焰的数值还是有问题
可以使用Debug.WriteLine打印信息
如
csharp
public void CalculateDamage()
{
Damage = (int)(Roll * MagicMultiplier) + BASE_DAMAGE + FlamingDamage;
Debug.WriteLine($"CalculateDamage finished: {Damage} (roll: {Roll})");
}
通过调试我们可以看出
程序调用的顺序有问题,先调用了SetFlaming函数,这个应该最后调用.程序没有按照我们想要的方式运行
第二版
思考错误原因
- 方法误用,调用的顺序不对
- 设置Roll后,要立即计算伤害
- 思考公共和私有方法
csharp
using System;
using System.Collections.Generic;
using System.Text;
namespace SwordDamage_WPF_Part_2
{
class SwordDamage
{
private const int BASE_DAMAGE = 3;
private const int FLAME_DAMAGE = 2;
/// <summary>
/// Contains the calculated damage.
/// </summary>
public int Damage { get; private set; }
private int roll;
/// <summary>
/// Sets or gets the 3d6 roll.
/// </summary>
public int Roll
{
get { return roll; }
set
{
roll = value;
CalculateDamage();
}
}
private bool magic;
/// <summary>
/// True if the sword is magic, false otherwise.
/// </summary>
public bool Magic
{
get { return magic; }
set
{
magic = value;
CalculateDamage();
}
}
private bool flaming;
/// <summary>
/// True if the sword is flaming, false otherwise.
/// </summary>
public bool Flaming
{
get { return flaming; }
set
{
flaming = value;
CalculateDamage();
}
}
/// <summary>
/// Calculates the damage based on the current properties.
/// </summary>
private void CalculateDamage()
{
decimal magicMultiplier = 1M;
if (Magic) magicMultiplier = 1.75M;
Damage = BASE_DAMAGE;
Damage = (int)(Roll * magicMultiplier) + BASE_DAMAGE;
if (Flaming) Damage += FLAME_DAMAGE;
}
/// <summary>
/// The constructor calculates damage based on default Magic
/// and Flaming values and a starting 3d6 roll.
/// </summary>
/// <param name="startingRoll">Starting 3d6 roll</param>
public SwordDamage(int startingRoll)
{
roll = startingRoll;
CalculateDamage();
}
}
}
给改代码之后,在使用Roll时,可以观察到,会调用计算伤害的函数,计算伤害的函数是按照顺序计算的
csharp
public int Roll
{
get { return roll; }
set
{
roll = value;
CalculateDamage();
}
}
而在计算伤害的函数中,按照顺序计算各个数值
csharp
private void CalculateDamage()
{
decimal magicMultiplier = 1M;
if (Magic) magicMultiplier = 1.75M;
Damage = BASE_DAMAGE;
Damage = (int)(Roll * magicMultiplier) + BASE_DAMAGE;
if (Flaming) Damage += FLAME_DAMAGE;
}
至此,我们就学习完了第五章,然后让我们复习一下本章讲了什么
- 修正Bug之前思考导致的真正的原因
- 如何思考修改Bug
- 使用Debug.WriteLine跟踪错误信息
- 封装的一些做法
- 将程序逻辑封装好,防止被随意使用