refactor(detail): 控制器按 ddCode 走 ChartStrategyRegistry 分派, 未注册优雅降级 (替代硬编码 dd_inversion_data)
- IDatasetChartStrategy + ChartStrategyRegistry 下移到控制器层 (src/controller, namespace geopro::controller), 删 app 层那份, 修层级倒置 (控制器不得依赖 app) - 接口加 hasGridPhase(); ErtInversionStrategy 留 app 层, 改继承 controller 接口, hasGridPhase()=true - DatasetDetailController 构造注入 ChartStrategyRegistry&; openDataset 用 registry.supports 降级; loadGridData 用 strategy->hasGridPhase 判定 - main.cpp 构造 registry 注册 ErtInversionStrategy 并注入 (registry 先于 detailCtrl 声明) - 测试: registry 加 hasGridPhase 断言; 控制器加空注册表降级 + 无网格阶段跳过网格加载用例; 全量 109/109 绿 (基线 106)
This commit is contained in:
parent
62352395ba
commit
0cb0ed8aa0
|
|
@ -81,6 +81,7 @@
|
|||
#include "ProjectListDialog.hpp"
|
||||
#include "WorkbenchNavController.hpp"
|
||||
#include "DatasetDetailController.hpp"
|
||||
#include "panels/chart/ErtInversionStrategy.hpp"
|
||||
#include "api/ApiProjectRepository.hpp"
|
||||
#include "api/ApiDatasetRepository.hpp"
|
||||
#include "panels/ObjectTreePanel.hpp"
|
||||
|
|
@ -860,7 +861,10 @@ int main(int argc, char* argv[])
|
|||
|
||||
// 数据详情仓储 + 控制器(接真实反演 API):同一共享会话 ApiClient。
|
||||
geopro::data::ApiDatasetRepository datasetRepo(api);
|
||||
geopro::controller::DatasetDetailController detailCtrl(datasetRepo);
|
||||
// 策略注册表须比 detailCtrl 活得久(同作用域、在前声明)。
|
||||
geopro::controller::ChartStrategyRegistry chartRegistry;
|
||||
chartRegistry.add(std::make_unique<geopro::app::ErtInversionStrategy>());
|
||||
geopro::controller::DatasetDetailController detailCtrl(datasetRepo, chartRegistry);
|
||||
|
||||
// ── 外壳:标准 QMainWindow(原生标题栏)。buildWorkbench 直接用其
|
||||
// setCentralWidget/setMenuWidget/statusBar 承载工作台。
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#pragma once
|
||||
#include "panels/chart/IDatasetChartStrategy.hpp"
|
||||
#include "IDatasetChartStrategy.hpp" // geopro::controller(geopro_controller PUBLIC include)
|
||||
namespace geopro::app {
|
||||
struct ErtInversionStrategy : IDatasetChartStrategy {
|
||||
// ERT 反演策略:散点(chart) + 网格等值面(grid) 两阶段。
|
||||
struct ErtInversionStrategy : controller::IDatasetChartStrategy {
|
||||
std::string ddCode() const override { return "dd_inversion_data"; }
|
||||
bool hasGridPhase() const override { return true; } // 反演有网格数据阶段
|
||||
};
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
#include "api/DatasetLoadHandles.hpp"
|
||||
namespace geopro::controller {
|
||||
|
||||
DatasetDetailController::DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent)
|
||||
: QObject(parent), repo_(repo) {}
|
||||
DatasetDetailController::DatasetDetailController(data::IAsyncDatasetRepository& repo,
|
||||
ChartStrategyRegistry& registry, QObject* parent)
|
||||
: QObject(parent), repo_(repo), registry_(registry) {}
|
||||
|
||||
DatasetDetailController::~DatasetDetailController() {
|
||||
if (chartLoad_) chartLoad_->abort(); // 退出契约:abort 在飞句柄,不依赖外部析构顺序兜底
|
||||
|
|
@ -12,7 +13,7 @@ DatasetDetailController::~DatasetDetailController() {
|
|||
}
|
||||
|
||||
void DatasetDetailController::openDataset(const QString& dsId, const QString& ddCode) {
|
||||
if (ddCode != QLatin1String("dd_inversion_data")) { // 首版仅支持 ERT 反演
|
||||
if (!registry_.supports(ddCode.toStdString())) { // 未注册策略 → 优雅降级
|
||||
emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览"));
|
||||
return;
|
||||
}
|
||||
|
|
@ -40,7 +41,8 @@ void DatasetDetailController::openDataset(const QString& dsId, const QString& dd
|
|||
}
|
||||
|
||||
void DatasetDetailController::loadGridData(const QString& dsId, const QString& ddCode) {
|
||||
if (ddCode != QLatin1String("dd_inversion_data")) return; // 仅 ERT 反演有网格数据
|
||||
auto* strategy = registry_.find(ddCode.toStdString());
|
||||
if (!strategy || !strategy->hasGridPhase()) return; // 仅有网格阶段的类型加载网格数据
|
||||
if (gridLoad_) gridLoad_->abort(); // abort-and-replace
|
||||
data::GridLoad* load = repo_.loadGridAsync(dsId.toStdString());
|
||||
gridLoad_ = load;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "model/Field.hpp"
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Anomaly.hpp"
|
||||
#include "IDatasetChartStrategy.hpp"
|
||||
namespace geopro::data { class IAsyncDatasetRepository; class ChartLoad; class GridLoad; }
|
||||
namespace geopro::controller {
|
||||
|
||||
|
|
@ -32,7 +33,8 @@ public:
|
|||
std::vector<geopro::core::Anomaly> anomalies;
|
||||
};
|
||||
|
||||
explicit DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent = nullptr);
|
||||
DatasetDetailController(data::IAsyncDatasetRepository& repo,
|
||||
ChartStrategyRegistry& registry, QObject* parent = nullptr);
|
||||
~DatasetDetailController() override; // 退出契约(spec §7):abort 在飞句柄,避免迟到信号打到已析构 this
|
||||
public slots:
|
||||
void openDataset(const QString& dsId, const QString& ddCode);
|
||||
|
|
@ -46,6 +48,7 @@ signals:
|
|||
void loadFailed(const QString& dsId, const QString& message);
|
||||
private:
|
||||
data::IAsyncDatasetRepository& repo_;
|
||||
ChartStrategyRegistry& registry_;
|
||||
QPointer<data::ChartLoad> chartLoad_;
|
||||
QPointer<data::GridLoad> gridLoad_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
namespace geopro::app {
|
||||
|
||||
class DatasetDetailPage; // 前置
|
||||
namespace geopro::controller {
|
||||
|
||||
// dd 类型驱动的图表策略:决定某 ddCode 的详情页如何加载/渲染。
|
||||
struct IDatasetChartStrategy {
|
||||
virtual ~IDatasetChartStrategy() = default;
|
||||
virtual std::string ddCode() const = 0;
|
||||
// 该类型是否有「网格数据」加载阶段(ERT 反演=true;纯散点/折线/图像类=false)。
|
||||
// 控制器据此决定是否允许 loadGridData,替代硬编码 ddCode 判断。
|
||||
virtual bool hasGridPhase() const = 0;
|
||||
};
|
||||
|
||||
class ChartStrategyRegistry {
|
||||
|
|
@ -26,4 +27,4 @@ public:
|
|||
private:
|
||||
std::map<std::string, std::unique_ptr<IDatasetChartStrategy>> map_;
|
||||
};
|
||||
} // namespace geopro::app
|
||||
} // namespace geopro::controller
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include "panels/chart/IDatasetChartStrategy.hpp"
|
||||
using namespace geopro::app;
|
||||
#include "IDatasetChartStrategy.hpp" // geopro::controller(控制器层)
|
||||
using namespace geopro::controller;
|
||||
namespace {
|
||||
struct Fake : IDatasetChartStrategy { std::string ddCode() const override { return "dd_inversion_data"; } };
|
||||
struct Fake : IDatasetChartStrategy {
|
||||
std::string ddCode() const override { return "dd_inversion_data"; }
|
||||
bool hasGridPhase() const override { return true; }
|
||||
};
|
||||
}
|
||||
TEST(ChartStrategyRegistry, FindsRegisteredAndDegradesUnknown) {
|
||||
ChartStrategyRegistry reg;
|
||||
|
|
@ -12,3 +15,10 @@ TEST(ChartStrategyRegistry, FindsRegisteredAndDegradesUnknown) {
|
|||
EXPECT_FALSE(reg.supports("dd_unknown"));
|
||||
EXPECT_EQ(reg.find("dd_unknown"), nullptr);
|
||||
}
|
||||
TEST(ChartStrategyRegistry, ReportsHasGridPhase) {
|
||||
ChartStrategyRegistry reg;
|
||||
reg.add(std::make_unique<Fake>());
|
||||
auto* s = reg.find("dd_inversion_data");
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->hasGridPhase());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,29 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <QSignalSpy>
|
||||
#include "DatasetDetailController.hpp"
|
||||
#include "IDatasetChartStrategy.hpp"
|
||||
#include "repo/IAsyncDatasetRepository.hpp"
|
||||
#include "api/DatasetLoadHandles.hpp"
|
||||
using namespace geopro;
|
||||
|
||||
namespace {
|
||||
// 反演策略桩:散点 + 网格两阶段。
|
||||
struct InversionStrategy : controller::IDatasetChartStrategy {
|
||||
std::string ddCode() const override { return "dd_inversion_data"; }
|
||||
bool hasGridPhase() const override { return true; }
|
||||
};
|
||||
// 无网格阶段策略桩:仅散点(如纯散点类型)。
|
||||
struct NoGridStrategy : controller::IDatasetChartStrategy {
|
||||
std::string ddCode() const override { return "dd_scatter_only"; }
|
||||
bool hasGridPhase() const override { return false; }
|
||||
};
|
||||
// 注册了反演策略的注册表(多数用例复用)。
|
||||
controller::ChartStrategyRegistry makeInversionRegistry() {
|
||||
controller::ChartStrategyRegistry reg;
|
||||
reg.add(std::make_unique<InversionStrategy>());
|
||||
return reg;
|
||||
}
|
||||
|
||||
// 桩句柄:不声明 Q_OBJECT —— 发射继承自 data::ChartLoad/GridLoad 的信号、override abort。
|
||||
struct StubChartLoad : data::ChartLoad {
|
||||
bool aborted = false;
|
||||
|
|
@ -33,7 +51,8 @@ struct StubAsyncRepo : data::IAsyncDatasetRepository {
|
|||
|
||||
TEST(DatasetDetailController, EmitsChartReadyOnDone) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry();
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::chartReady);
|
||||
c.openDataset("ds1", "dd_inversion_data");
|
||||
repo.lastChart->fireDone();
|
||||
|
|
@ -42,7 +61,8 @@ TEST(DatasetDetailController, EmitsChartReadyOnDone) {
|
|||
|
||||
TEST(DatasetDetailController, EmitsLoadFailedOnFailed) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry();
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
|
||||
c.openDataset("ds1", "dd_inversion_data");
|
||||
repo.lastChart->fireFailed();
|
||||
|
|
@ -51,16 +71,39 @@ TEST(DatasetDetailController, EmitsLoadFailedOnFailed) {
|
|||
|
||||
TEST(DatasetDetailController, UnsupportedTypeFailsImmediately) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry(); // 注册了反演,但未注册 dd_other
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
|
||||
c.openDataset("ds1", "dd_other");
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
EXPECT_EQ(repo.lastChart, nullptr); // 未发起加载
|
||||
}
|
||||
|
||||
// 空注册表 → 任意 ddCode 都不支持 → loadFailed,不发起加载。
|
||||
TEST(DatasetDetailController, EmptyRegistryFailsAnyType) {
|
||||
StubAsyncRepo repo;
|
||||
controller::ChartStrategyRegistry reg; // 空
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
|
||||
c.openDataset("ds1", "dd_inversion_data");
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
EXPECT_EQ(repo.lastChart, nullptr);
|
||||
}
|
||||
|
||||
// 无网格阶段的策略 → loadGridData 不发起网格加载。
|
||||
TEST(DatasetDetailController, NoGridPhaseStrategySkipsGridLoad) {
|
||||
StubAsyncRepo repo;
|
||||
controller::ChartStrategyRegistry reg;
|
||||
reg.add(std::make_unique<NoGridStrategy>());
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
c.loadGridData("ds1", "dd_scatter_only");
|
||||
EXPECT_EQ(repo.lastGrid, nullptr); // hasGridPhase()==false → 未发起
|
||||
}
|
||||
|
||||
TEST(DatasetDetailController, AbortsPreviousOnReopen) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry();
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
c.openDataset("dsA", "dd_inversion_data");
|
||||
StubChartLoad* a = repo.lastChart;
|
||||
c.openDataset("dsB", "dd_inversion_data"); // 替换
|
||||
|
|
@ -69,7 +112,8 @@ TEST(DatasetDetailController, AbortsPreviousOnReopen) {
|
|||
|
||||
TEST(DatasetDetailController, DropsLateSignalFromAbortedLoad) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry();
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::chartReady);
|
||||
c.openDataset("dsA", "dd_inversion_data");
|
||||
StubChartLoad* a = repo.lastChart;
|
||||
|
|
@ -83,7 +127,8 @@ TEST(DatasetDetailController, DropsLateSignalFromAbortedLoad) {
|
|||
|
||||
TEST(DatasetDetailController, EmitsGridReadyOnDone) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry();
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::gridReady);
|
||||
c.loadGridData("ds1", "dd_inversion_data");
|
||||
repo.lastGrid->fireDone();
|
||||
|
|
@ -92,7 +137,8 @@ TEST(DatasetDetailController, EmitsGridReadyOnDone) {
|
|||
|
||||
TEST(DatasetDetailController, EmitsLoadFailedOnGridFailed) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry();
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
|
||||
c.loadGridData("ds1", "dd_inversion_data");
|
||||
repo.lastGrid->fireFailed();
|
||||
|
|
@ -101,7 +147,8 @@ TEST(DatasetDetailController, EmitsLoadFailedOnGridFailed) {
|
|||
|
||||
TEST(DatasetDetailController, DropsLateGridSignalFromAbortedLoad) {
|
||||
StubAsyncRepo repo;
|
||||
controller::DatasetDetailController c(repo);
|
||||
auto reg = makeInversionRegistry();
|
||||
controller::DatasetDetailController c(repo, reg);
|
||||
QSignalSpy spy(&c, &controller::DatasetDetailController::gridReady);
|
||||
c.loadGridData("dsA", "dd_inversion_data");
|
||||
StubGridLoad* a = repo.lastGrid;
|
||||
|
|
|
|||
Loading…
Reference in New Issue